Java实现7种常见密码算法( 二 )


密码学哈希密码学哈希算法包括MD5、SHA1、SHA256等 , 在JCA中都使用MessageDigest类即可 , 如下:
public static String sha256(byte[] bytes) throws NoSuchAlgorithmException {MessageDigest digest = MessageDigest.getInstance("SHA-256");digest.update(bytes);return Hex.encodeHexString(digest.digest());}消息认证码消息认证码使用Mac类实现 , 以常见的HMAC搭配SHA256为例 , 如下:
public byte[] digest(byte[] data, Key key) throws InvalidKeyException, NoSuchAlgorithmException{Mac mac = Mac.getInstance("HmacSHA256");mac.init(key);return mac.doFinal(data);}数字签名数字签名使用Signature类实现 , 以RSA搭配SHA256为例 , 如下:
public byte[] sign(byte[] data, PrivateKey privateKey) {try {Signature signature = Signature.getInstance("SHA256withRSA");signature.initSign(privateKey);signature.update(data);return signature.sign();} catch (Exception e) {return ExceptionUtils.rethrow(e);}}public boolean verify(byte[] data, PublicKey publicKey, byte[] sign) {try {Signature signature = Signature.getInstance("SHA256withRSA");signature.initVerify(publicKey);signature.update(data);return signature.verify(sign);} catch (Exception e) {return ExceptionUtils.rethrow(e);}}密钥协商算法在JCA中 , 使用KeyAgreement来调用密钥协商算法 , 以ECDH协商算法为例 , 如下:
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));}

经验总结扩展阅读