Android keyStore系统存储的RSA密钥,加解密处理
Android有keysrore可以存储密钥,RSA密钥对中,公钥可以取出,私钥不能取出只能使用。本文只看23就是6.0及以上。
生产密钥对
生成密钥的参数。
spec = new KeyGenParameterSpec.Builder(mAlias, KeyProperties.PURPOSE_SIGN
| KeyProperties.PURPOSE_ENCRYPT
| KeyProperties.PURPOSE_DECRYPT)
.setKeySize(DEFAULT_KEY_SIZE)
.setUserAuthenticationRequired(false)
.setCertificateSubject(new X500Principal("CN=" + mAlias))
//, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA384,
// KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_MD5)
.setDigests(KeyProperties.DIGEST_SHA512,KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_MD5)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.build();
使用参数
KeyProperties.PURPOSE_SIGN
| KeyProperties.PURPOSE_ENCRYPT
| KeyProperties.PURPOSE_DECRYP
这三个是声明密钥是可以用来干啥的:加密,解密和签名。
注意:私钥不能用来加密,取私钥加密的时候会报key操作失败。可能是我用的不对,但是目前是这样。因为公钥是明文,私钥加密无法对数据进行保护。
签名算法
setDigests(KeyProperties.DIGEST_SHA512,KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_MD5)
如果没有申明使用,会报操作失败。
加解密填充
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1,KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
里面虽然有PKCS7,但是不是给给RSA使用的。这个pkcs1也不是对称密钥的那种填充规则。啰嗦一下。
同样,这里如果不申明,后续填充是无法使用的。
Android支持的RSA操作官方文档描述如下:
RSA/ECB/NoPadding 18+
RSA/ECB/PKCS1Padding 18+
RSA/ECB/OAEPWithSHA-1AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-224AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-256AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-384AndMGF1Padding 23+
RSA/ECB/OAEPWithSHA-512AndMGF1Padding 23+
RSA/ECB/OAEPPadding 23+
加解密开始
加密
keyStore里面的生成的密钥长度可以是K:1024,2048。目前常用是这两个其他的不考虑。那么rsa加密最长数据段为:K/8.就是1024/8或者2048/8.
以2048为例最大数据长度为256.
如果我们的数据长度超过256,就需要进行分段加密。比较非对称密钥计算时间太长,一般也不会使用太长数据。
然后就是填充:
- 如果是无填充,那么数据长度就是256.
- 如果是pcks1,那么最大数据长度是256-11=245
- 如果是OAEPWithSHA-**AndMGF1Padding 代表哈希数,那么明文最大长度是:256-2(/8)-2
- OAEPPadding 这个好像默认是使用sha-1。有待研究。
所以根据上面的填充规则,我们数据段切割的时候,就需要注意长度。
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
int inputLen = needEncryptWord.length();
byte[] inputData = needEncryptWord.getBytes();
for (int i = 0; inputLen - offSet > 0; offSet = i * blockSize) {//最大长度245(256-11)PKCS1
byte[] cache;
if (inputLen - offSet > rsaEncryptBlock) {
cache = inCipher.doFinal(inputData, offSet, blockSize);
} else {
cache = inCipher.doFinal(inputData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
++i;
}
byte[] encryptedData = out.toByteArray();
out.close();
填充完毕,数据合并,一次加密完成。
解密
解密就比较简单了。同样是分段解密,注意数据长度:就是类似:2048/8。具体的看自己的密钥长度。
for (int i = 0; inputLen - offSet > 0; offSet = i * rsaDecryptBlock) {//rsaDecryptBlock=2048/8
byte[] cache;
if (inputLen - offSet > rsaDecryptBlock) {
cache = outCipher.doFinal(encryptedData, offSet, rsaDecryptBlock);
} else {
cache = outCipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
++i;
}
分段解密,合并数据。
签名
签名部分直接贴代码:
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
Signature s = Signature.getInstance("SHA1withRSA");
s.initSign(privateKeyEntry.getPrivateKey());
s.update(data.getBytes());
return Base64.encodeToString(s.sign(),0);
验签
KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(alias, null);
Signature s = Signature.getInstance("SHA1withRSA");
s.initVerify(privateKeyEntry.getCertificate());
s.update(srcData.getBytes());
return s.verify(Base64.decode(sign.getBytes(), 0));
关于签名部分的填充,暂时没看明白,希望得到大佬们的指点。
以上,完成。谢谢。希望大佬们留言讨论。