SecureRandom生成“強隨機數”用於生成RSA*公鑰/私鑰*window和linux下不一致的問題
阿新 • • 發佈:2019-02-04
1.先說下問題:
由於我們的服務部署環境是兩臺伺服器,在服務啟動時生成RSA金鑰對。這有一個問題:當兩臺機器分別啟動時,生成了不同的金鑰對。而當客戶端需要用到RSA加解密的時候,連結可能會被負載到另一臺機器上,造成解密失敗,丟擲異常。
2.看下之前的程式碼(第1版):
private static final KeyPair keyPair = initKey();
private static KeyPair initKey() {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(512, random);
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
其中SecureRandom random = new SecureRandom();在生成強隨機數時,沒有指定任何引數。所以生成的隨機數是無法控制的。通過查詢API,發現另一個構造方法new SecureRandom(byte[] b),繼而可以通過指定固定引數,返回固定的SecureRandom物件。故改出第2版:
private static final KeyPair keyPair = initKey();
private static KeyPair initKey() {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte b[] = "abc123".getBytes();
SecureRandom random = new SecureRandom(b);
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(512, random);
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
經本地測試,可以滿足每次生成的金鑰對是一樣的。但是打包到測試伺服器後,發現沒有起作用,依然是不一樣的金鑰對。開始抓狂……,然後各種百度,得知win和linux處理方法是不一樣,根據找到的資料,重新調整下。第3版:
private static KeyPair initKey() {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte b[] = "abc123".getBytes();
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
secureRandom.setSeed(b);
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(512,secureRandom);
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
經測試,win和linux都能完美相容。到此暫告一段落。
其他兩個生成publicKey和解密方法,也貼上:
/**
* 生成public key
* @return
*/
public static String generateBase64PublicKey() {
RSAPublicKey pubkey = (RSAPublicKey) keyPair.getPublic();
return new String(Base64.encodeBase64(pubkey.getEncoded()));
}
/**
* 解密
* @param string
* @return
*/
public static String decryptBase64(String string) {
return new String(decrypt(Base64.decodeBase64(string)));
}
private static byte[] decrypt(byte[] string) {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding", "BC");
RSAPrivateKey pbk = (RSAPrivateKey) keyPair.getPrivate();
cipher.init(Cipher.DECRYPT_MODE, pbk);
byte[] plainText = cipher.doFinal(string);
return plainText;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
又經安全考慮,覺得把用於生成祕鑰的放在程式碼裡不安全,故需要存放到資料庫中。但是在寫程式碼的時候發現,在RSAUtils工具類中,spring無法注入,呼叫不到service層查詢資料庫。經各種百度查詢後,修改RSAUtils工具類初始化載入如下:
@Component
public class RSAUtils {
private static final Logger log = LoggerFactory.getLogger(RSAUtils.class);
private static KeyPair keyPair = null;
@Autowired
private ISysParamService sysParamService;
private static RSAUtils rsaUtils;
@PostConstruct
public void init(){
rsaUtils = this;
rsaUtils.sysParamService = this.sysParamService;
keyPair = initKey();
}
private static KeyPair initKey() {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String keyStr = rsaUtils.sysParamService.getVal("key","key"); //service方法,查詢到該字串
byte b[] = keyStr.getBytes();
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
secureRandom.setSeed(b);
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
generator.initialize(512,secureRandom);
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
至此,完成此需求的調整開發。記錄下來。