1. 程式人生 > >SecureRandom生成“強隨機數”用於生成RSA*公鑰/私鑰*window和linux下不一致的問題

SecureRandom生成“強隨機數”用於生成RSA*公鑰/私鑰*window和linux下不一致的問題

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);
                            }
                    }
            }

至此,完成此需求的調整開發。記錄下來。