2018-08-23遇到的一点小问题
公司最近和银联合作,要求接口请求必须加密。网上搜一下有很多相关内容,这边贴几个有参考到的。
Android数据加密
AES加密CBC模式兼容互通四种编程语言平台
Java进行 RSA 加解密时不得不考虑到的那些事儿
AES加密
/**
* AES加密
*/
public static String aesEncrypt(String content, String encryptKey, String encryptIv) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
byte[] dataBytes = content.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(encryptKey.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(encryptIv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return Base64Coder.encodeLines(encrypted);
} catch (Exception e) {
e.printStackTrace();
Log.e("wannoo", "ASE加密出错:", e);
return null;
}
}
公司使用的AES选择的CBC模式,还要有偏移量,服务端给的,长度9位,因为PHP和IOS竟然都没有长度限制,能正常测试。然而我是需要16位(128 bits),不然会报错,这个网上在线加密,有的会提示,有的不会。然后和服务端协商后统一给的16位。
java.security.InvalidAlgorithmParameterException: expected IV length of 16 but was 9
at com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER.engineInitInternal(OpenSSLCipher.java:512)
at com.android.org.conscrypt.OpenSSLCipher.engineInit(OpenSSLCipher.java:274)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:973)
at javax.crypto.Cipher.init(Cipher.java:908)
然后服务端给的秘钥是9位,我使用的这个方法会出错。没办法,网上又找方法解决。有的方法出来的文本竟然不一样,试了几次才解决。不过因为后面服务端又给了长度16位的秘钥,所以方法删掉了,下次有需要还要网上找。
java.security.InvalidKeyException: Unsupported key size: 9 bytes
at com.android.org.conscrypt.OpenSSLCipher$EVP_CIPHER$AES.checkSupportedKeySize(OpenSSLCipher.java:733)
at com.android.org.conscrypt.OpenSSLCipher.checkAndSetEncodedKey(OpenSSLCipher.java:443)
at com.android.org.conscrypt.OpenSSLCipher.engineInit(OpenSSLCipher.java:273)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2659)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2570)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2475)
at javax.crypto.Cipher.chooseProvider(Cipher.java:566)
at javax.crypto.Cipher.init(Cipher.java:973)
at javax.crypto.Cipher.init(Cipher.java:908)
然后说一下获取的数据byte[] encrypted = cipher.doFinal(plaintext);
不能直接new String()获取,需要base64或HEX转换,具体看服务端需求。这一点包括生成AES加密秘钥时的处理也一样。
/**
* 随机生成秘钥
*/
public static String getAesKey() {
try {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128);
SecretKey sk = kg.generateKey();
byte[] b = sk.getEncoded();
return byteToHexString(b);
} catch (Exception e) {
e.printStackTrace();
Log.e("wannoo", "生成ASE秘钥出错:", e);
}
return null;
}
MD5加密
这里顺便贴一下MD5加密的代码,至于其他的下次再说。
/**
* MD5加密
*/
public static String md5Encrypt(String str) {
if (str != null && !str.equals("")) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
byte[] md5Byte = md5.digest(str.getBytes("UTF8"));
StringBuilder sb = new StringBuilder();
for (byte aMd5Byte : md5Byte) {
sb.append(HEX[(int) (aMd5Byte & 0xff) / 16]);
sb.append(HEX[(int) (aMd5Byte & 0xff) % 16]);
}
str = sb.toString();
} catch (Exception e) {
Log.e("wannoo", "MD5加密出错", e);
}
}
return str;
}
RSA加密
我们进行的RSA加密是服务端给公钥,我们加密后发送数据,服务端用私钥解密。
收到的公钥是类似这样子的,很长一串。
-----BEGIN PUBLIC KEY-----
QWERTYUIOPpoiuytrewq
asdfghjklAYLsU50c7gK5OxTrfdqe
ZXCVBNMEulERs6a2rVCo5xCVoCuJjq
1234567890mqv6CwIDAQAB
-----END PUBLIC KEY-----
然后加密时就一堆问题了,现在随便列几个:
java.security.spec.InvalidKeySpecException: key spec not recognized
这个错误是使用PKCS8EncodedKeySpec
获取公钥对象,参考这个。
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object java.lang.Object.clone()' on a null object reference
这个错误是使用X509EncodedKeySpec
获取公钥对象时,没去掉 -----BEGIN PUBLIC KEY-----
和-----END PUBLIC KEY-----
。
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
这个错误说是因为Android版本问题,具体是不是我就不懂了,参考这个。
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unknown tag 13 encountered
这个错误是直接将服务端给的公钥用.getBytes();
获取byte[]
时出错的
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: Extra data detected in stream
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: failed to construct sequence from byte[]: unexpected end-of-contents marker
上面那两个是将公钥用Base64转byte[]时出错的。找了很久原因才发现我之前用的Base64库不适用这个。还找了个包含sun.misc.BASE64Encoder
的jar包,可惜不行。最后上Github找了一个,没问题了。
贴一下里的客户端获取公钥和私钥的方法,测试时可以用。
SecureRandom sr = new SecureRandom();//RSA算法要求有一个可信任的随机数源
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");//为RSA算法创建一个KeyPairGenerator对象
kpg.initialize(KEYSIZE, sr);//利用上面的随机数据源初始化这个KeyPairGenerator对象
KeyPair kp = kpg.generateKeyPair();//生成密匙对
Key publicKey = kp.getPublic();//得到公钥
byte[] publicKeyBytes = publicKey.getEncoded();
String pub = new String(Base64.encodeBase64(publicKeyBytes),"UTF-8");
Key privateKey = kp.getPrivate();// 得到私钥
byte[] privateKeyBytes = privateKey.getEncoded();
String pri = new String(Base64.encodeBase64(privateKeyBytes),"UTF-8");
然后贴一下用公钥进行加密的步骤
encryptKey = encryptKey.replaceAll("-----BEGIN PUBLIC KEY-----", "");
encryptKey = encryptKey.replaceAll("-----END PUBLIC KEY-----", "");
/**
* RSA加密
*/
public static String rsaEncrypt(String content, String encryptKey) {
try {
byte[] buffer = Base64Coder2.decode2(encryptKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] dataBytes = content.getBytes();
byte[] encrypted = cipher.doFinal(dataBytes);
return Base64Coder.encodeLines(encrypted);
} catch (Exception e) {
e.printStackTrace();
Log.e("wannoo", "RSA加密出错:", e);
return null;
}
}