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算法加密模式有ECB、CBC、CFBCTR、GCM等,填充模式有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反序列化時沒有做反轉義,這會導致兩邊計算的簽名值不一樣,如下:

推薦閱讀