Java實現7種常見密碼算法( 二 )


public static void testEcdh() {KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");keyGen.initialize(ecSpec);// A生成自己的私密信息KeyPair keyPairA = keyGen.generateKeyPair();KeyAgreement kaA = KeyAgreement.getInstance("ECDH");kaA.init(keyPairA.getPrivate());// B生成自己的私密信息KeyPair keyPairB = keyGen.generateKeyPair();KeyAgreement kaB = KeyAgreement.getInstance("ECDH");kaB.init(keyPairB.getPrivate());// B收到A發送過來的公用信息,計算出對稱密鑰kaB.doPhase(keyPairA.getPublic(), true);byte[] kBA = kaB.generateSecret();// A收到B發送過來的公開信息,計算對對稱密鑰kaA.doPhase(keyPairB.getPublic(), true);byte[] kAB = kaA.generateSecret();Assert.isTrue(Arrays.equals(kBA, kAB), "協商的對稱密鑰不一致");}基于口令加密PBE通常,對稱加密算法需要使用128位字節的密鑰 , 但這么長的密鑰用戶是記不住的,用戶容易記住的是口令,也即password,但與密鑰相比,口令有如下弱點:

  1. 口令通常較短 , 這使得直接使用口令加密的強度較差 。
  2. 口令隨機性較差,因為用戶一般使用較容易記住的東西來生成口令 。
為了使得用戶能直接使用口令加密,又能最大程度避免口令的弱點,于是PBE(Password Based Encryption)算法誕生 , 思路如下:
  1. 既然密碼算法需要密鑰,那在加解密前,先使用口令生成密鑰,然后再使用此密鑰去加解密 。
  2. 為了彌補口令隨機性較差的問題,生成密鑰時使用隨機鹽來混淆口令來產生準密鑰,再使用散列函數對準密鑰進行多次散列迭代,以生成最終的密鑰 。
因此,使用PBE算法進行加解密時,除了要提供口令外,還需要提供隨機鹽(salt)與迭代次數(iteratorCount),如下:
public static byte[] encrypt(byte[] plainBytes, String password, byte[] salt, int iteratorCount) {try {PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES").generateSecret(keySpec);Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES");cipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(salt, iteratorCount));byte[] encryptBytes = cipher.doFinal(plainBytes);byte[] iv = cipher.getIV();ByteArrayOutputStream baos = new ByteArrayOutputStream(iv.length + encryptBytes.length);baos.write(iv);baos.write(encryptBytes);return baos.toByteArray();} catch (Exception e) {throw Errors.toRuntimeException(e);}}public static byte[] decrypt(byte[] secretBytes, String password, byte[] salt, int iteratorCount) {try {PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndTripleDES").generateSecret(keySpec);Cipher cipher = Cipher.getInstance("PBEWithMD5AndTripleDES");IvParameterSpec ivParameterSpec = new IvParameterSpec(secretBytes, 0, cipher.getBlockSize());cipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(salt, iteratorCount, ivParameterSpec));return cipher.doFinal(secretBytes, cipher.getBlockSize(), secretBytes.length - cipher.getBlockSize());} catch (Exception e) {throw Errors.toRuntimeException(e);}}public static void main(String[] args) throws Exception {byte[] content = "hello".getBytes(StandardCharsets.UTF_8);byte[] salt = Base64.decode("QBadPOP6/JM=");String password = "password";byte[] encoded = encrypt(content, password, salt, 1000);System.out.println("密文:" + Base64.encode(encoded));byte[] plainBytes = decrypt(encoded, password, salt, 1000);System.out.println("明文:" + new String(plainBytes, StandardCharsets.UTF_8));}注意,雖然使用PBE加解密數據,都需要使用相同的password、salt、iteratorCount , 但這里面只有password是需要保密的,salt與iteratorCount不需要 , 可以保存在數據庫中,比如每個用戶注冊時給他生成一個隨機鹽 。
到此 , JCA密碼算法就介紹完了,來回顧一下:
Java實現7種常見密碼算法

文章插圖
整體來說 , JCA對密碼算法相關的類設計與封裝還是非常清晰簡單的!
但使用密碼算法時,依賴SecretKey、PublicKey、PrivateKey對象提供密鑰信息,那這些密鑰對象是怎么來的呢?
密鑰生成與讀取密碼學隨機數密碼學隨機數算法在安全場景中使用廣泛,如:生成對稱密鑰、鹽、iv等,因此相比普通的隨機數算法(如線性同余),它需要更高強度的不可預測性,在Java中,使用SecureRandom來生成更安全的隨機數 , 如下:
public class SecureRandoms { public static byte[] randBytes(int len) throws NoSuchAlgorithmException {byte[] bytes = new byte[len];SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");secureRandom.nextBytes(bytes);return bytes; }}

推薦閱讀