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

SecureRandom使用了更高强度的随机算法,同时会读取机器本身的随机熵值,如/dev/urandom , 因此相比普通的Random,它具有更强的随机性,因此,对于需要生成密钥的场景,该用哪个要拧得清 。
对称密钥在JCA中对称密钥使用SecretKey表示,若要生成一个新的SecretKey,可使用KeyGenerator,如下:
//生成新的密钥public static SecretKey genSecretKey() {KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");keyGenerator.init(SecureRandom.getInstance("SHA1PRNG"));SecretKey secretKey = keyGenerator.generateKey();}而如果是从文件中读取密钥的话 , 则可以借助SecretKeyFactory将其转换为SecretKey,如下:
//读取密钥public static SecretKey getSecretKey() {byte[] keyBytes = readKeyBytes();String alg = "AES";SecretKey secretKey = SecretKeyFactory.getInstance(alg).generateSecret(new SecretKeySpec(keyBytes, alg));}非对称密钥在JCA中,对于非对称密钥,公钥使用PublicKey表示 , 私钥使用PrivateKey表示,若要生成一个新的公私钥对,可使用KeyPairGenerator,如下:
//生成新的公私钥对public static void genKeyPair() {KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");keyPairGen.initialize(2048);KeyPair keyPair = keyPairGen.generateKeyPair();PublicKey publicKey = keyPair.getPublic();PrivateKey privateKey = keyPair.getPrivate();}而如果是从文件中读取公私钥的话,一般公钥是X509格式,而私钥是PKCS8格式,分别对应JCA中的X509EncodedKeySpec与PKCS8EncodedKeySpec , 如下:
//读取私钥public static PrivateKey getPrivateKey() {byte[] privateKeyBytes = readPrivateKeyBytes();PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(pkcs8EncodedKeySpec);}//读取公钥public static PublicKey getPublicKey() {byte[] publicKeyBytes = readPublicKeyBytes();X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);}注意,KeyGenerator、KeyPairGenerator与KeyFactory从命名上看起来有点相似,但它们实现的功能是完全不同的 , KeyGenerator、KeyPairGenerator用于生成新的密钥,而KeyFactory则用于将KeySpec转换为对应的Key密钥对象 。
JCA密钥相关类关系一览,如下:

Java实现7种常见密码算法

文章插图
Java实现7种常见密码算法

文章插图
常见问题密文无法解密问题有时,在使用密码算法时,会发现别人提供的密文使用正确的密钥却无法解密出来,特别容易发生在跨语言的情况下,如加密方使用的C#语言 , 而解密方却使用的Java 。
遇到这种情况,你需要和对方认真确认加密时使用的加密模式、填充模式以及IV等密码参数是否完全一致 。
如AES算法加密模式有ECBCBCCFBCTRGCM等,填充模式有PKCS#5, ISO 10126, ANSI X9.23等,以及对方是使用了固定的IV向量还是将IV向量拼在了密文中 , 这些都需要确认清楚并与对方保持一致才能正确解密 。
签名失败问题签名失败也是使用密码算法时常见的情况,比如对方生成的MD5值与你生成的MD5不一致,常见有2种原因,如下:1. 使用的字符编码不一致导致密码算法为了通用性,操作对象都是字节数组,而你要签名的对象一般是字符串 , 因此你需要将字符串转为字节数组之后再做md5运算 , 如下:
  • 调用方:md5(str.getBytes())
  • 服务方:md5(str.getBytes())
看起来两边的代码一模一样,但问题就在getBytes()函数中,getBytes()函数默认会使用操作系统的字符编码将字符串转为字节数组,而中文Windows默认字符编码是GBK,而Linux默认是UTF-8,这就导致当str中有中文时,调用方与服务方获取到的字节数组是不一样的 , 那生成的MD5值当然也不一样了 。
因此,强烈推荐在使用getBytes()函数时,传入统一的字符编码,如下:
  • 调用方:md5(str.getBytes("UTF-8"))
  • 服务方:md5(str.getBytes("UTF-8"))这样就能有效地避过这个非常隐晦的坑了 。
2. json的escape功能导致有些json框架 , 做json序列化时会默认做一些转义操作,如把&字符转义为\u0026 , 但如果服务端做json反序列化时没有做反转义,这会导致两边计算的签名值不一样,如下:

推荐阅读