非对称加密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[]);
4. 参考资料

参考资料优质参考资料
Java 进行 RSA 加解密时不得不考虑到的那些事儿