/* * The MIT License * * Copyright 2014-2015 Karol Bucek. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jruby.ext.openssl; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.Mac; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.PBEParametersGenerator; import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; import org.bouncycastle.crypto.params.KeyParameter; import org.jruby.Ruby; import org.jruby.RubyModule; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.anno.JRubyModule; import org.jruby.runtime.builtin.IRubyObject; /** * OpenSSL::PKCS5 * * @author kares */ @JRubyModule(name = "OpenSSL::PKCS5") public class PKCS5 { public static void createPKCS5(final Ruby runtime, final RubyModule ossl) { final RubyModule PKCS5 = ossl.defineModuleUnder("PKCS5"); PKCS5.defineAnnotatedMethods(PKCS5.class); } // def pbkdf2_hmac_sha1(pass, salt, iter, keylen) @JRubyMethod(meta = true, required = 4) public static IRubyObject pbkdf2_hmac_sha1(final IRubyObject self, final IRubyObject[] args) { //final byte[] pass = args[0].asString().getBytes(); final char[] pass = args[0].asString().toString().toCharArray(); final byte[] salt = args[1].asString().getBytes(); final int iter = (int) args[2].convertToInteger().getLongValue(); final int keySize = (int) args[3].convertToInteger().getLongValue(); // e.g. 64 return generatePBEKey(self.getRuntime(), pass, salt, iter, keySize); } // def pbkdf2_hmac_sha1(pass, salt, iter, keylen, digest) @JRubyMethod(meta = true, required = 5) public static IRubyObject pbkdf2_hmac(final IRubyObject self, final IRubyObject[] args) { final byte[] pass = args[0].asString().getBytes(); final byte[] salt = args[1].asString().getBytes(); final int iter = (int) args[2].convertToInteger().getLongValue(); final int keylen = (int) args[3].convertToInteger().getLongValue(); final String digestAlg; final IRubyObject digest = args[4]; if ( digest instanceof Digest ) { digestAlg = mapDigestName( ((Digest) digest).getRealName() ); } else { digestAlg = mapDigestName( digest.asString().toString() ); } // NOTE: on our own since e.g. "PBKDF2WithHmacMD5" not supported by Java final String macAlg = "Hmac" + digestAlg; final Ruby runtime = self.getRuntime(); try { final Mac mac = SecurityHelper.getMac( macAlg ); mac.init( new SimpleSecretKey(macAlg, pass) ); final byte[] key = deriveKey(mac, salt, iter, keylen); return StringHelper.newString(runtime, key); } catch (NoSuchAlgorithmException ex) { throw Utils.newRuntimeError(runtime, ex); // should no happen } catch (InvalidKeyException ex) { throw Utils.newRuntimeError(runtime, ex); // TODO } } private static String mapDigestName(final String name) { final String mapped = name.toUpperCase(); if ( mapped.startsWith("SHA-") ) { // SHA-512 return "SHA" + mapped.substring(4); // SHA512 } return mapped; } private static RubyString generatePBEKey(final Ruby runtime, final char[] pass, final byte[] salt, final int iter, final int keySize) { PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(); generator.init(PBEParametersGenerator.PKCS5PasswordToBytes(pass), salt, iter); CipherParameters params = generator.generateDerivedParameters(keySize * 8); return StringHelper.newString(runtime, ((KeyParameter) params).getKey()); } // http://stackoverflow.com/questions/9147463/java-pbkdf2-with-hmacsha256-as-the-prf public static byte[] deriveKey( final Mac prf, byte[] salt, int iterationCount, int dkLen ) throws NoSuchAlgorithmException, InvalidKeyException { // Note: hLen, dkLen, l, r, T, F, etc. are horrible names for // variables and functions in this day and age, but they // reflect the terse symbols used in RFC 2898 to describe // the PBKDF2 algorithm, which improves validation of the // code vs. the RFC. // // dklen is expressed in bytes. (16 for a 128-bit key) int hLen = prf.getMacLength(); // 20 for SHA1 int l = Math.max( dkLen, hLen); // 1 for 128bit (16-byte) keys int r = dkLen - (l-1)*hLen; // 16 for 128bit (16-byte) keys byte T[] = new byte[l * hLen]; int ti_offset = 0; for (int i = 1; i <= l; i++) { F( T, ti_offset, prf, salt, iterationCount, i ); ti_offset += hLen; } if (r < hLen) { // Incomplete last block byte DK[] = new byte[dkLen]; System.arraycopy(T, 0, DK, 0, dkLen); return DK; } return T; } private static void F( byte[] dest, int offset, Mac prf, byte[] S, int c, int blockIndex ) { final int hLen = prf.getMacLength(); byte U_r[] = new byte[ hLen ]; // U0 = S || INT (i); byte U_i[] = new byte[S.length + 4]; System.arraycopy( S, 0, U_i, 0, S.length ); doINT( U_i, S.length, blockIndex ); for( int i = 0; i < c; i++ ) { U_i = prf.doFinal( U_i ); doXOR( U_r, U_i ); } System.arraycopy( U_r, 0, dest, offset, hLen ); } private static void doXOR( byte[] dest, byte[] src ) { for( int i = 0; i < dest.length; i++ ) { dest[i] ^= src[i]; } } private static void doINT( byte[] dest, int offset, int i ) { dest[offset + 0] = (byte) (i / (256 * 256 * 256)); dest[offset + 1] = (byte) (i / (256 * 256)); dest[offset + 2] = (byte) (i / (256)); dest[offset + 3] = (byte) (i); } }