非对称加密RSA在Android中的应用
非对称加密RSA
1. SSH利用非对称加密实现免密登陆
- (1)本地客户端生成公私钥(密钥对):
ssh-keygen
,生成时所填写的用户名与密码不具效力,可有可无; - (2)执行命令1后,可在用户目录下的~/.ssh文件夹找到密钥对文件
id_rsa和id_rsa.pub
; - (3)上传公钥到需要登陆的服务器用户目录下
ssh-copy-id -i ~/.ssh/id_rsa.pub server_username@server_ip
; - (4)在
cd ~/.ssh
目录下,查看vim authorized_keys
文件,是否存在之前拷贝的id_rsa.pub的内容; - (5)随后,在本地客户端命令行,输入
ssh server_username@server_ip
登陆远程服务器用户; - (6)RSA:非对称加密算法,其安全性基于极其困难的大整数的分解(两个素数的乘积)
- (7)DSA:也是非对称加密算法,其安全性基于整数有限域离散对数难题;
2. 服务器-客户端之间的RSA非对称加密技术
2.1 工作原理
- 服务器生成密钥对,并把公钥发送给客户端,客户端使用公钥加密登陆密码和敏感数据,服务器使用私钥解密;
- 公钥加密后无法推算私钥,也无法解密得到数据;
- 公私钥成对地生成,作为钥匙,加解密系统作为标准化锁实体,可在别处按照密钥长度、加密规则重构锁实体,即在客户端重构;
2.2 实现流程
(1)生成密钥对:
public static Map<String, Object> initKey() throws Exception {
//实例化密钥生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); //加密方式
//初始化密钥生成器
keyPairGenerator.initialize(KEY_SIZE); //长度
//生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//甲方公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
//甲方私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
//将密钥存储在map中
Map<String, Object> keyMap = new HashMap<String, Object>();
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
(2)将密钥对转换为byte[]字节数组:
public static byte[] getPrivateKey(Map<String, Object> keyMap) {
Key key = (Key) keyMap.get(PRIVATE_KEY/PUBLIC_KEY);
return key.getEncoded();
}
(3)将字节数组进行BASE64转换,得到字符串,以便于存储发送:
Map<String, Object> map = ResUtil.initKey();
byte[] PrivateKey = ResUtil.getPrivateKey(map);
byte[] PublicKey = ResUtil.getPublicKey(map);
// 将公私钥转为base64-法1
String priEncBase64 = new String(Base64.encodeBase64(PrivateKey));
String pubEncBase64 = new String(Base64.encodeBase64(PublicKey));
// 将公私钥转为base64-法2
String priEncBase64 = Base64.encodeBase64String(PrivateKey);
String pubEncBase64 = Base64.encodeBase64String(PublicKey);
(4)使用公钥加密
/**
* 公钥加密
*
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密数据
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//数据加密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
(5)使用私钥解密
/**
* 私钥解密
*
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
//取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
//数据解密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
2.3 背景知识
(1)byte[]转String: new String(byte[])
或Base64.encodeBase64String(byte[])
或String pubEncBase64 = new String(Base64.encodeBase64(PublicKey))
(2)String转byte[]: string.getByte()
或Base64.decodeBase64()
3. javax.crypto.BadPaddingException: Decryption error 非对称加密报错
报错现象: 在服务端,相同公钥和报文,每次加密得到的密文都不一样;而在安卓端,相同情况下,得到的报文没有发生变化。
- android 端使用的默认填充方式是“RSA/None/NoPadding”,即不使用填充机制;
- 而服务端使用的是“RSA/None/PKCS1Padding”,使用随机填充机制;
- 填充的目的是拓展密文的长度;
- 原来android系统的RSA实现是"RSA/None/NoPadding",而标准JDK实现是"RSA/None/PKCS1Padding" ,这造成了在android机上加密后无法在服务器上解密的原因
- 还需要注意字节数组与字符串的类型转换问题
- 解决方法,在安卓端设置
Cipher.getInstance("RSA/ECB/PKCS1Padding")
- string to byte[]的方法:string.getByte()或者Base64.decodeBase64()
- byte[] to string的方法:new String(byte[])或者Base64.encodeBase64String(byte[]);