使用Spring Security Crypto模組進行AES-256加解密
Introduction
本文主要使用Spring Security Crypto Module對文字進行加解密,以及對密碼進行單向不可逆的加密實作。此模組提供對稱加密、密鑰生成及密碼編碼的服務,雖然模組被分配在核心模組裡,但與其他Spring Security(或Spring)無關。
Encryptors
此class提供工廠模式來建構對稱加密器,可創建ByteEncryptors來以原生byte[]方式對資料做加密,也可以建構TextEncryptors對文字做加密。Encryptors是thread-safe執行緒安全的,不用擔心在多執行緒下存取資料會出問題。實作中使用ByteEncryptors來產生加密器,產生方式有standard及stronger,兩者都是使用256-bit AES加密方式,差別在於stronger加入了Galois Counter Mode(GCM)讓加密方法更強,也建議使用Encryptors.stronger
來產生BytesEncryptor:
Integer KEY_LENGTH = 32;
byte[] SECRET_KEY = KeyGenerators.secureRandom(KEY_LENGTH).generateKey();
String PASSWORD = new String(Hex.encode(SECRET_KEY));
String SALT = KeyGenerators.string().generateKey();
BytesEncryptor ENCRYPTORS = Encryptors.stronger(PASSWORD, SALT);
需要注意的是,以上的PASSWORD及SALT是隨機產生的,在專案裡必需固定或另外寫程式定期更換,否則每一次重新啟動專案,會因為不同的值導致無法正確解密回原本的文字。
Encrypt & Decrypt
產生加密器之後,即可針對文字進行加密及解密。由於實作使用的是ByteEncryptors,所以加密後會另外轉成Base64.encodeBase64String
的String型別再存進資料庫;同理,從資料庫取出加密文字後,需轉成Byte型別方能進行解密。
public static String encrypt(String stringToEncrypt) throws UnsupportedEncodingException {
byte[] byteToEncrypt = stringToEncrypt.getBytes(StandardCharsets.UTF_8.toString());
byte[] encryptByte = ENCRYPTORS.encrypt(byteToEncrypt);
return Base64.encodeBase64String(encryptByte);
}
public static String decrypt(String stringToDecrypt) throws UnsupportedEncodingException {
byte[] byteToDecrypt = stringToDecrypt.getBytes(StandardCharsets.UTF_8.toString());
byte[] decryptByte = ENCRYPTORS.decrypt(Base64.decodeBase64(byteToDecrypt));
return new String(decryptByte);
}
Password Encoding
此模組也提供了密碼加密的服務,本文使用BCryptPasswordEncoder來實作PasswordEncoder:
PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
PasswordEncoder界面提供以下方法:
public interface PasswordEncoder {
String encode(String rawPassword);
boolean matches(String rawPassword, String encodedPassword);
}
BCryptPasswordEncoder是使用bcrypt演算法來hash密碼,可以使用matches方法透過加密使用者輸入的文字,來與資料庫裡加密過的密碼進行比對。
換句話說,密碼加密是單向不可逆的,即使哪天資料外洩,駭客也不會知道實際的明碼。Bcrypt使用16 byte隨機的salt值來放慢算法,目的是要阻止密碼破解者,可以使用強度來調整所做的工作量,強度的範圍在4~31,預設是10,強度越大,密碼hash的工作量越多。因為強度值有被編碼到hash裡面,所以可以隨時更改,不會影響到現有的密碼。
public static String encryptPassword(String passwordToEncrypt) {
return PASSWORD_ENCODER.encode(passwordToEncrypt);
}
public static boolean isPasswordMatch(String rawPassword, String encodedPassword) {
return PASSWORD_ENCODER.matches(rawPassword, encodedPassword);
}
References:
- https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/crypto.html
- https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/encrypt/Encryptors.html