PKCS#5 v2.0 java 語言實現參考

鄒德強發表於2012-04-06

再接再厲:PKCS#5的實現:

PKCS#5主要講了基於口令的加密標準,具體包括:

1.金鑰派生:

1.1 金鑰派生介面

package com.broadthinking.pkcs.pkcs_5;

/**
 * A typical application of the key derivation functions defined here might
 * include the following steps: 
 * 1. Select a salt S and an iteration count c, as outlined in Section 4. 
 * 2. Select a length in octets for the derived key,dkLen. 
 * 3. Apply the key derivation function to the password, the salt, the iteration 
 * 		count and the key length to produce a derived key. 
 * 4. Output the derived key.
 * 
 * @author CaesarZou
 * 
 */
public interface IPBKDF {
	/**
	 * produce a derived key. 
	 * @param P
	 * @param S
	 * @param c
	 * @param dkLen
	 * @return DK
	 */
	public byte [] derive(byte [] P, byte [] S, long c, int dkLen) throws PKCS5Exception;
}

1.2金鑰派生實現版本1 

package com.broadthinking.pkcs.pkcs_5;

import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class PBKDF1 implements IPBKDF {

	public PBKDF1(MessageDigest Hash) {
		this.hash = Hash;
	}
	
	//default use SHA-1
	public PBKDF1() {
		try {
			this.hash = MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
		}
	}
	
	/**
	 * PBKDF1 (P, S, c, dkLen)
	 * Options: Hash underlying hash function
	 * Input: 
	 * 		P 		password, an octet string
	 * 		S 		salt, an eight-octet string
	 * 		c 		iteration count, a positive integer
	 * 		dkLen 	intended length in octets of derived key, a positive integer, at most
	 * 					16 for MD2 or MD5 and 20 for SHA-1
	 * Output:	DK derived key, a dkLen-octet string
	 * @throws PKCS5Exception 
	 * 
	 */
	@Override
	public byte[] derive(byte[] P, byte[] S, long c, int dkLen) throws PKCS5Exception {
		
		int hLen = hash.getDigestLength();
		
		//1. If dkLen > 16 for MD2 and MD5, or dkLen > 20 for SHA-1, output “derived key
	    //	too long” and stop.
		if(dkLen > hLen) {
			throw new PKCS5Exception(PKCS5Exception.ERROR_PBKDF_DERIVEDKEY_TOO_LONG);
		}
		
		/*
		 * 2. Apply the underlying hash function Hash for c iterations to the
		 * concatenation of the password P and the salt S, then extract the
		 * first dkLen octets to produce a derived key DK: T1 = Hash (P || S) ,
		 * T2 = Hash (T1) , ... Tc = Hash (Tc-1) , DK = Tc<0..dkLen-1> .
		 */
		byte [] T = new byte [hLen];

		try {
			hash.reset();
			hash.update(P);
			hash.update(S);
			for(long i=0;i<c;i++) {
				hash.digest(T, 0, hLen);
				hash.update(T);
			}
		} catch (DigestException e) {
			//ignore
		}
		
		byte [] DK = new byte[(int)dkLen];
		System.arraycopy(T, 0, DK, 0, (int)dkLen);
		
		//3. Output the derived key DK.
		return DK;
	}

	MessageDigest hash;
	
}

1.3金鑰派生版本2

package com.broadthinking.pkcs.pkcs_5;

public class PBKDF2 implements IPBKDF {

	public PBKDF2() {
		prf = new HMAC_SHA_1();
	}
	
	/**
	 * PBKDF2 (P, S, c, dkLen)
	 * Options: PRF underlying pseudorandom function (hLen denotes the length in
     *      octets of the pseudorandom function output)
	 * Input: 
	 * 			P 		password, an octet string
	 * 			S 		salt, an octet string
	 * 			c 		iteration count, a positive integer
	 * 			dkLen 	intended length in octets of the derived key, a positive integer, at
	 * 					most (232 – 1) × hLen
	 * Output:	DK		derived key, a dkLen-octet string
	 * @throws PKCS5Exception 
	 *
	 */
	@Override
	public byte[] derive(byte[] P, byte[] S, long c, int dkLen) throws PKCS5Exception {
		//1. If dkLen > (2^32 – 1) × hLen, output “derived key too long” and stop.
		//int value can never big then 2^32-1
		byte [] DK = new byte[dkLen];
		/*
		 * 2. Let l be the number of hLen-octet blocks in the derived key, rounding up, and let r
  		 * be the number of octets in the last block:
		 * l = dkLen / hLen ,
		 * r = dkLen – (l – 1) × hLen .
		 */
		int hLen = prf.getDigestLen();
		
		
		int l = (dkLen/hLen) + (dkLen%hLen==0 ? 0: 1);
		//3. For each block of the derived key apply the function F defined below to the
		// password P, the salt S, the iteration count c, and the block index to compute the
		// block:
		for(int i=0;i<l;i++) {
			//Ti = F (P, S, c, i) ,
			byte [] T = new byte[hLen];
			//F (P, S, c, i) = U1 \xor U2 \xor ⋅⋅⋅ \xor Uc
			byte [] U = null;
			for(int j=0;j<c;j++) {
				if(j==0) {
					U = new byte[S.length+4];
					System.arraycopy(S, 0, U, 0, S.length);	//S
					U[S.length+0] = (byte)(i>>24);			//INT(i)
					U[S.length+1] = (byte)(i>>16);
					U[S.length+2] = (byte)(i>>8);
					U[S.length+3] = (byte)(i>>0);
				}
				//U1 = PRF (P, S || INT (i)) , U2 = PRF (P, U1) , ... Uc = PRF(P, Uc-1) .
				U = prf.digest(P, U);
				for(int k=0;k<hLen;k++) {
					T[i] ^= U[i];
				}
			}
			
			//4.Concatenate the blocks and extract the first dkLen octets to produce a derived key
			// DK = T1 || T2 || ⋅⋅⋅ || Tl<0..r-1> .
			int length = hLen;
			if(i==(l-1)) {
				length = dkLen - (i*hLen);
			}
			System.arraycopy(T, 0, DK, i*hLen, length);
		}
		
		//5. Output the derived key DK.
		return DK;
	}

	HMAC_SHA_1	prf;
}

2.基於金鑰派生和塊加密演算法的加密/解密操作。

2.1 塊加密演算法介面

package com.broadthinking.pkcs.pkcs_5;

public interface IBlockCipher {
	public byte [] encrypt(byte [] DK, byte [] M) throws PKCS5Exception;
	public byte [] decrypt(byte [] DK, byte [] C) throws PKCS5Exception;
}

2.2 塊加密演算法例項:DES_CBC_PKCS5Padding

package com.broadthinking.pkcs.pkcs_5;


import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class BlockCipher_DES_CBC_Pad implements IBlockCipher {

	public BlockCipher_DES_CBC_Pad() {
		try {
			des = Cipher.getInstance("DES/CBC/PKCS5Padding");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public byte [] encrypt(byte[] DK, byte[] M) throws PKCS5Exception {
	
		return op(Cipher.ENCRYPT_MODE, DK, M);
	}

	@Override
	public byte [] decrypt(byte[] DK, byte[] C) throws PKCS5Exception{
		return op(Cipher.DECRYPT_MODE, DK, C);
	}

	private byte[] op(int mode, byte [] DK, byte [] data) throws PKCS5Exception {
		
		if(DK.length != 16) {
			throw new PKCS5Exception(PKCS5Exception.ERROR_BLOCKCIPHER_WRONG_KEY_LEN);
		}
		byte [] key  = new byte[8];
		byte [] iv = new byte[8];
		System.arraycopy(DK, 0, key, 0, 8);
		System.arraycopy(DK, 8, iv, 0, 8);
		
		try {
			DESKeySpec deskeySpec = new DESKeySpec(key);
			Key deskey = SecretKeyFactory.getInstance("DES").generateSecret(deskeySpec);
			
			IvParameterSpec param = new IvParameterSpec(iv);
			des.init(mode, deskey, param);
			return des.doFinal(data);
			
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	Cipher des;

	
}


2.3 塊加密演算法例項:DES_EDE3_CBC_PKCS5Padding


package com.broadthinking.pkcs.pkcs_5;

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class BlockCipher_DES_EDE3_CBC_Pad implements IBlockCipher {

	public BlockCipher_DES_EDE3_CBC_Pad() {
		try {
			des = Cipher.getInstance("DESede/CBC/PKCS5Padding");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public byte[] encrypt(byte[] DK, byte[] M) throws PKCS5Exception {
		
		return op(Cipher.ENCRYPT_MODE, DK, M);
	}

	@Override
	public byte[] decrypt(byte[] DK, byte[] C) throws PKCS5Exception{
		
		return op(Cipher.DECRYPT_MODE, DK, C);
	}
	
	private byte[] op(int mode, byte [] DK, byte [] data) throws PKCS5Exception {
		if(DK.length != 32) {
			throw new PKCS5Exception(PKCS5Exception.ERROR_BLOCKCIPHER_WRONG_KEY_LEN);
		}
		byte [] key  = new byte[24];
		byte [] iv = new byte[8];
		System.arraycopy(DK, 0, key, 0, 24);
		System.arraycopy(DK, 24, iv, 0, 8);
		
		try {
			DESedeKeySpec deskeySpec = new DESedeKeySpec(key);
			Key deskey = SecretKeyFactory.getInstance("DES").generateSecret(deskeySpec);
			
			IvParameterSpec param = new IvParameterSpec(iv);
			des.init(mode, deskey, param);
			return des.doFinal(data);
			
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	Cipher des;
}

2.4 塊加密演算法例項:RC2_CBC_PKCS5Padding


package com.broadthinking.pkcs.pkcs_5;

import java.security.Key;


import javax.crypto.Cipher;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.RC2ParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class BlockCipher_RC2_CBC_Pad implements IBlockCipher {

	public BlockCipher_RC2_CBC_Pad(int keybits) {
		try {
			rc2 = Cipher.getInstance("RC2/CBC/PKCS5Padding");
			effective_key_bits = keybits;
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
	public BlockCipher_RC2_CBC_Pad() {
		this(32);
	}
	
	@Override
	public byte[] encrypt(byte[] DK, byte[] M) throws PKCS5Exception {
		
		return op(Cipher.ENCRYPT_MODE, DK, M);
	}

	@Override
	public byte[] decrypt(byte[] DK, byte[] C) throws PKCS5Exception {
		
		return op(Cipher.DECRYPT_MODE, DK, C);
	}
	
	private byte[] op(int mode, byte [] DK, byte [] data) throws PKCS5Exception {
		
		//key between 1 - 128
		if((DK.length < 9) || (DK.length > 136)) {
			throw new PKCS5Exception(PKCS5Exception.ERROR_BLOCKCIPHER_WRONG_KEY_LEN);
		}
		byte [] key  = new byte[DK.length-8];
		byte [] iv = new byte[8];
		System.arraycopy(DK, 0, key, 0, key.length);
		System.arraycopy(DK, key.length, iv, 0, 8);
		
		try {
			SecretKeySpec rc2keySPec = new SecretKeySpec(key,"RC2");
			Key rckey = SecretKeyFactory.getInstance("RC2").generateSecret(rc2keySPec);
			
			RC2ParameterSpec param = new RC2ParameterSpec(effective_key_bits, iv);
			rc2.init(mode, rckey, param);
			return rc2.doFinal(data);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	

	Cipher rc2;
	int	   effective_key_bits;
}

2.5 加解密操作1


package com.broadthinking.pkcs.pkcs_5;

public class PBES1 {

	public PBES1(IPBKDF kdf, IBlockCipher cipher) {
		
		//only support DES_CBC_PAD/RC2_CBC_PAD
		this.kdf	= kdf;
		//only support MD2/MD5/SHA-1
		this.cipher = cipher;
	}
	
	public PBES1() {
		this.kdf	= new PBKDF1();
		this.cipher	= new BlockCipher_DES_CBC_Pad();
	}
	
	public byte [] encrypt(byte [] M, byte [] P, byte [] S, long c) throws PKCS5Exception {
		
		//1. Select an eight-octet salt S and an iteration count c, as outlined in Section 4.
		
		//2. Apply the PBKDF1 key derivation function (Section 5.1) to the password P, the
		//	 salt S, and the iteration count c to produce a derived key DK of length 16 octets:
		//			DK = PBKDF1 (P, S, c, 16) .
		byte [] DK = kdf.derive(P, S, c, 16);
		
		//3. Separate the derived key DK into an encryption key K consisting of the first eight
		//	 octets of DK and an initialization vector IV consisting of the next eight octets:
		//4. Concatenate M and a padding string PS to form an encoded message EM:
		//5. Encrypt the encoded message EM with the underlying block cipher 
		//6. Output the ciphertext C.
		return cipher.encrypt(DK, M);
	}
	
	public byte [] decrypt(byte [] C, byte [] P, byte [] S, long c) throws PKCS5Exception {
		
		//1. Obtain the eight-octet salt S and the iteration count c.
		
		//2. Apply the PBKDF1 key derivation function (Section 5.1) to the password P, the
		//	 salt S, and the iteration count c to produce a derived key DK of length 16 octets:
		//			DK = PBKDF1 (P, S, c, 16) .
		byte [] DK = kdf.derive(P, S, c, 16);
		
		//3. Separate the derived key DK into an encryption key K consisting of the first eight
		//	 octets of DK and an initialization vector IV consisting of the next eight octets:
		
		//4. Decrypt the ciphertext C with the underlying block cipher
		//5. Separate the encoded message EM into a message M and a padding string PS
		//6. Output the recovered message M.
		return cipher.decrypt(DK, C);
	}
	
	IBlockCipher 	cipher;
	IPBKDF			kdf;
}

2.6 加解密操作2


package com.broadthinking.pkcs.pkcs_5;

public class PBES2 {
	
	public PBES2(IPBKDF kdf, IBlockCipher cipher) {
		this.kdf = kdf;
		this.cipher = cipher;
	}
	
	public PBES2() {
		this.kdf = new PBKDF2();
		this.cipher = new BlockCipher_DES_CBC_Pad();
	}
	public byte [] encrypt(byte [] M, byte [] P, byte [] S, long c, int dkLen) throws PKCS5Exception {
		
		//1. Select a salt S and an iteration count c, as outlined in Section 4.
		
		//2. Select the length in octets, dkLen, for the derived key for the underlying encryption scheme.
		
		//3. Apply the selected key derivation function to the password P, the salt S, and the
		//	 iteration count c to produce a derived key DK of length dkLen octets:
		//			DK = KDF (P, S, c, dkLen) .
		byte [] DK = kdf.derive(P, S, c, dkLen);
		
		//4. Encrypt the message M with the underlying encryption scheme under the derived
		//   key DK to produce a ciphertext C.
		//5. Output the ciphertext C.
		return cipher.encrypt(DK, M);
	}
	
	public byte [] decrypt(byte [] C, byte [] P, byte [] S, long c, int dkLen) throws PKCS5Exception {
		
		//1. Obtain the salt S for the operation.
		//2. Obtain the iteration count c for the key derivation function.
		//3. Obtain the key length in octets, dkLen, for the derived key for the underlying
		//   encryption scheme.
		//4. Apply the selected key derivation function to the password P, the salt S, and the
		//   iteration count c to produce a derived key DK of length dkLen octets:
		//			DK = KDF (P, S, c, dkLen) .
		byte [] DK = kdf.derive(P, S, c, dkLen);
		
		//5. Decrypt the ciphertext C with the underlying encryption scheme under the derived
		//   key DK to recover a message M. If the decryption function outputs “decryption
		//   error,” then output “decryption error” and stop.
		//6. Output the recovered message M.
		return cipher.decrypt(DK, C);

	}
	
	IBlockCipher cipher;
	IPBKDF kdf;
}

3.基於金鑰派生和MAC演算法的訊息校驗碼生成和驗證操作。

3.1 MAC演算法

package com.broadthinking.pkcs.pkcs_5;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HMAC_SHA_1 {
	
	public HMAC_SHA_1() {
		try {
			this.hash = MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
			//ignore
		}
	} 
	
	public byte [] digest(byte [] key, byte [] text) {
		hash.reset();
		hash.update(key);
		hash.update(text);
		return hash.digest();
	}
	
	public int getDigestLen() {
		return hash.getDigestLength();
	}

	MessageDigest hash;
}

3.2 訊息驗證碼生成/驗證


package com.broadthinking.pkcs.pkcs_5;

public class PBMAC1 {
	
	public PBMAC1() {
		this.kdf = new PBKDF2();
		this.macauth = new HMAC_SHA_1();
	}
	
	public byte [] sign(byte [] M, byte [] P, byte [] S, long c, int dkLen) throws PKCS5Exception {
		
		//1. Select a salt S and an iteration count c, as outlined in Section 4.
		//2. Select a key length in octets, dkLen, for the derived key for the underlying message
		//   authentication function.
		//3. Apply the selected key derivation function to the password P, the salt S, and the
		//  iteration count c to produce a derived key DK of length dkLen octets:
		//			DK = KDF (P, S, c, dkLen) .
		byte [] DK = kdf.derive(P, S, c, dkLen);
		//4. Process the message M with the underlying message authentication scheme under
		//  the derived key DK to generate a message authentication code T.
		//5. Output the message authentication code T.
		return macauth.digest(DK, M);
	}
	
	
	public boolean verify(byte [] M, byte [] P, byte [] S, long c, int dkLen, byte [] EM) throws PKCS5Exception {
		//1. Obtain the salt S and the iteration count c.
		//2. Obtain the key length in octets, dkLen, for the derived key for the underlying
		//  message authentication scheme.
		//3. Apply the selected key derivation function to the password P, the salt S, and the
		//  iteration count c to produce a derived key DK of length dkLen octets:
		//  		DK = KDF (P, S, c, dkLen) .
		byte [] DK = kdf.derive(P, S, c, dkLen);
		//4. Process the message M with the underlying message authentication scheme under
		//  the derived key DK to verify the message authentication code T.
		byte [] T = macauth.digest(DK, M);
		
		if(T.length != EM.length) {
			return false;
		}
		
		for(int i=0;i<T.length;i++) {
			if(T[i]!=EM[i]) {
				return false;
			}
		}
		
		return true;
	}
	
	
	IPBKDF kdf;
	HMAC_SHA_1 macauth;
}

4. 異常


package com.broadthinking.pkcs.pkcs_5;

public class PKCS5Exception extends Exception {

	public PKCS5Exception(int errorcode) {
		super("PKCS5Exception "+errorcode);
		this.errorcode = errorcode;
	}
	
	public static final int ERROR_PBKDF_DERIVEDKEY_TOO_LONG	= 1;
	public static final int ERROR_BLOCKCIPHER_WRONG_KEY_LEN = 10;
	/**
	 * 
	 */
	private static final long serialVersionUID = -6228905820937466408L;

	int errorcode;
}

5.測試


package com.broadthinking.pkcs.pkcs_5.test;

import com.broadthinking.pkcs.pkcs_5.PBES1;
import com.broadthinking.pkcs.pkcs_5.PBES2;
import com.broadthinking.pkcs.pkcs_5.PKCS5Exception;

public class TestVect {

	public static void printHex(String Message, byte [] M) {
		printHex(Message, M, 0, M.length);
	}
	
	public static void printHex(String Message, byte [] M, int offset , int length) {
		System.out.print("# " + Message);
		System.out.print(": ");
		for(int i=0;i<length;i++) {
			if((i%16)==0) {
				System.out.println();
			}
			System.out.print(String.format("%02x ", M[offset+i]));
		}
		System.out.println();
		System.out.println();
	}
	/**
	 * @param args
	 * @throws PKCS5Exception 
	 */
	public static void main(String[] args) throws PKCS5Exception {
		
		byte [] M = {1,2,3,4,5};
		byte [] P = {(byte)0x98,0x76,0x54,0x32,(byte)0x10};
		byte [] S = {9,9,9,9,9,9,9,9,9};
		
		PBES1 esf = new PBES1();
		byte [] C = esf.encrypt(M, P, S, 10);
		byte [] M2 = esf.decrypt(C, P, S, 10);
		printHex("M", M);
		printHex("M2",M2);
		
		PBES2 esf2 = new PBES2();
		C = esf2.encrypt(M2, P, S, 10, 16);
		M2 = esf2.decrypt(C, P, S, 10, 16);
		printHex("M3",M2);
		
		System.out.println("OVER");
	}

}



相關文章