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);
}
}
}
至此,完成此需求的调整开发。记录下来。