【RSA】rsa加密算法使用

近日遇到一个漏洞修复需求,需要把一个老系统登录时请求里的明文密码进行加密再传输。由于系统太老,也不方便对数据库用户密码(后台收到密码后使用另一套加密算法加密后保存)进行修改,只能在前端加密后由后端再解密出来,不太适合用不可逆的加密算法来操作。于是打算使用RSA算法进行加密解密(中间还用了base64编码密码后传输,被打回了hhh)。

RSA加密算法是一种非对称加密算法,使用时需要生成成对的公钥和私钥。一般使用公钥加密,私钥解密,根据具体需求反过来使用也行。公钥可以对外公布,私钥一般只有内部知道。可以根据私钥来生成公钥,反之不行。

  1. 私钥用于签名、公钥用于验签
    签名和加密作用不同,签名并不是为了保密,而是为了保证这个签名是由特定的某个人签名的,而不是被其它人伪造的签名,所以私钥的私有性就适合用在签名用途上。
    私钥签名后,只能由对应的公钥解密,公钥又是公开的(很多人可持有),所以这些人拿着公钥来解密,解密成功后就能判断出是持有私钥的人做的签名,验证了身份合法性。

  2. 公钥用于加密、私钥用于解密,这才能起到加密作用
    因为公钥是公开的,很多人可以持有公钥。若用私钥加密,那所有持有公钥的人都可以进行解密,这是不安全的!
    若用公钥加密,那只能由私钥解密,而私钥是私有不公开的,只能由特定的私钥持有人解密,保证的数据的安全性。

生成公钥和私钥

这个有多种生成方式,可以去网上百度在线生成,也可以代码生成,或者使用命令行生成。
我这里使用命令行本地生成,打开任意文件夹,右键git bash here打开git窗口,运行命令

1
2
// 生成RSA私钥,文件名为private.pem,长度为1024bit
openssl genrsa -out private.pem 1024

这里生成的是1024位的私钥,需要更安全可以生成更多位。
然后生成公钥

1
2
// 从私钥中提取公钥
openssl rsa -in private.pem -pubout -out public.pem

文件夹中会生成private.pem和public.pem两个文件。打开就可以看到私钥和公钥信息。
我这里测试生成了一对,应该是这样的

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDPLCuyRx54V/OPNyRRBZ9ATfNEKfBbc6jOeQT2D6FwpNG06qDL
lLT9sRvuJu/T1kYRTRWIp9Wstkp/7yLowZQzNxA2DzHj9kZotIzRuBqY+a0PRqFt
3cVOaKodA/HHwuEVtI5CPKRJJtYbg7OPIxQrGpNNTNe14Ez06T6VKSAUsQIDAQAB
AoGACMgcihANsJg50MZLmcudNoKXXzpP3/CFJUtn9G4xL68s3HhdnIOPMHnDb1Lj
M52Plsgns4U5v3pyqf2fAzLBb17FQoUPr0IkvEzYV4QYURFRjk8GsJM8LinbemH7
x9ljp54N6+24vIa0lD+utRaH3xKl1bQa21ymZiTsW1cnSMkCQQD0VcuzhYTrMbRV
N7qCgicP5/fk6TOYYBwgOkEh0GrGpypJmYv5mGFB7HMRoFVM+D0Woa60UHCSPF6r
cIGihztjAkEA2RAuzEPXOeUc6YCNrWjJfeTTgPWjVP3bSZNoX6CMnM1FYrRkGDn1
lY9Cr1xS+Nq44zz3zhwN399N3zXQ2jbN2wJBAJ1kuzCsvP/o59rRaGLzxof3jPe3
xZXNq7CS9iv7Hx1Sx+nbcJDbOSOHVmSvXOOVMXznsCvVeX6qRu23Lrrs1DMCQC0b
as4x7uDuFrMDbu2xT+XkjntHEHkSA+bnRhJRN8dB9QBNAlvyd3FsAuyUH/3s3e0C
93ASbmOucO1Irq0aJG8CQFOQ/dYFYLaHa3IeGWzi6aSPB4FSsr8pcNppYDvLWq6V
pGVVW5Ks/nodNBQ4bM+n/JkAjSZxcPIOwdwce6EEgeQ=
-----END RSA PRIVATE KEY-----

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPLCuyRx54V/OPNyRRBZ9ATfNE
KfBbc6jOeQT2D6FwpNG06qDLlLT9sRvuJu/T1kYRTRWIp9Wstkp/7yLowZQzNxA2
DzHj9kZotIzRuBqY+a0PRqFt3cVOaKodA/HHwuEVtI5CPKRJJtYbg7OPIxQrGpNN
TNe14Ez06T6VKSAUsQIDAQAB
-----END PUBLIC KEY-----

前端使用公钥加密密码

项目较老,用的原生js,需要先引入加密工具类jsencrypt.min.js,具体可以去官网下载或者引用在线资源

https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.min.js

var encryptor = new JSEncrypt();  
var pubKey = "-----BEGIN PUBLIC KEY-----\n" +  
  "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPLCuyRx54V/OPNyRRBZ9ATfNE\n" +  
  "KfBbc6jOeQT2D6FwpNG06qDLlLT9sRvuJu/T1kYRTRWIp9Wstkp/7yLowZQzNxA2\n" +  
  "DzHj9kZotIzRuBqY+a0PRqFt3cVOaKodA/HHwuEVtI5CPKRJJtYbg7OPIxQrGpNN\n" +  
  "TNe14Ez06T6VKSAUsQIDAQAB\n" +  
  "-----END PUBLIC KEY-----\n";
encryptor.setPublicKey(pubKey);  
aObj.fullUrl = requesturl.replace("{2}","JSONscriptRequest.removeScriptTagById('" + aObj.getScriptTagId() + "')").replace("{0}", username).replace("{1}",encodeURIComponent(encryptor.encrypt(password))).replace("{3}",callback);

公钥不需要变,原样复制过来就行。至于密码用了encodeURIComponent去编码一下是因为我使用时发现加密后的字符串里的特殊字符没有成功传到后台。encodeURIComponent可以把这些特殊字符编码不会被请求自动过滤替换

后台使用私钥解密

起初我使用私钥字符串去生成RSAPrivateKey类的时候,总是报各种错,无法生成。后来各种测试私钥的使用,终于成功了。
想不读取文件的形式,使用字符串去生成RSAPrivateKey类,在java里需要先对私钥做一点处理工作。

  • 首先需要把我们的私钥从PKCS#1转化成PKCS#8
    PKCS#1的私钥是BEGIN RSA PRIVATE KEY开头的,PKCS#8的私钥是BEGIN PRIVATE KEY开头的。转化依然可以找在线网站转化一下。

    http://www.metools.info/code/c84.html

  • 转化完成后,把转化完成的私钥去掉头尾的标记复制出来,如下(不要有空格换行之类的)

    MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM8sK7JHHnhX8483
    JFEFn0BN80Qp8FtzqM55BPYPoXCk0bTqoMuUtP2xG+4m79PWRhFNFYin1ay2Sn/v
    IujBlDM3EDYPMeP2Rmi0jNG4Gpj5rQ9GoW3dxU5oqh0D8cfC4RW0jkI8pEkm1huD
    s48jFCsak01M17XgTPTpPpUpIBSxAgMBAAECgYAIyByKEA2wmDnQxkuZy502gpdf
    Ok/f8IUlS2f0bjEvryzceF2cg48wecNvUuMznY+WyCezhTm/enKp/Z8DMsFvXsVC
    hQ+vQiS8TNhXhBhREVGOTwawkzwuKdt6YfvH2WOnng3r7bi8hrSUP661FoffEqXV
    tBrbXKZmJOxbVydIyQJBAPRVy7OFhOsxtFU3uoKCJw/n9+TpM5hgHCA6QSHQasan
    KkmZi/mYYUHscxGgVUz4PRahrrRQcJI8XqtwgaKHO2MCQQDZEC7MQ9c55RzpgI2t
    aMl95NOA9aNU/dtJk2hfoIyczUVitGQYOfWVj0KvXFL42rjjPPfOHA3f303fNdDa
    Ns3bAkEAnWS7MKy8/+jn2tFoYvPGh/eM97fFlc2rsJL2K/sfHVLH6dtwkNs5I4dW
    ZK9c45UxfOewK9V5fqpG7bcuuuzUMwJALRtqzjHu4O4WswNu7bFP5eSOe0cQeRID
    5udGElE3x0H1AE0CW/J3cWwC7JQf/ezd7QL3cBJuY65w7UiurRokbwJAU5D91gVg
    todrch4ZbOLppI8HgVKyvylw2mlgO8tarpWkZVVbkqz+eh00FDhsz6f8mQCNJnFw
    8g7B3Bx7oQSB5A==
    
  • 放入java代码中使用,我的解密部分代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//base64编码的私钥
String thekey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM8sK7JHHnhX848" +
"JFEFn0BN80Qp8FtzqM55BPYPoXCk0bTqoMuUtP2xG+4m79PWRhFNFYin1ay2Sn/v" +
"IujBlDM3EDYPMeP2Rmi0jNG4Gpj5rQ9GoW3dxU5oqh0D8cfC4RW0jkI8pEkm1huD" +
"s48jFCsak01M17XgTPTpPpUpIBSxAgMBAAECgYAIyByKEA2wmDnQxkuZy502gpdf" +
"Ok/f8IUlS2f0bjEvryzceF2cg48wecNvUuMznY+WyCezhTm/enKp/Z8DMsFvXsVC" +
"hQ+vQiS8TNhXhBhREVGOTwawkzwuKdt6YfvH2WOnng3r7bi8hrSUP661FoffEqXV" +
"tBrbXKZmJOxbVydIyQJBAPRVy7OFhOsxtFU3uoKCJw/n9+TpM5hgHCA6QSHQasan" +
"KkmZi/mYYUHscxGgVUz4PRahrrRQcJI8XqtwgaKHO2MCQQDZEC7MQ9c55RzpgI2t" +
"aMl95NOA9aNU/dtJk2hfoIyczUVitGQYOfWVj0KvXFL42rjjPPfOHA3f303fNdDa" +
"Ns3bAkEAnWS7MKy8/+jn2tFoYvPGh/eM97fFlc2rsJL2K/sfHVLH6dtwkNs5I4dW" +
"ZK9c45UxfOewK9V5fqpG7bcuuuzUMwJALRtqzjHu4O4WswNu7bFP5eSOe0cQeRID" +
"5udGElE3x0H1AE0CW/J3cWwC7JQf/ezd7QL3cBJuY65w7UiurRokbwJAU5D91gVg" +
"todrch4ZbOLppI8HgVKyvylw2mlgO8tarpWkZVVbkqz+eh00FDhsz6f8mQCNJnFw" +
"8g7B3Bx7oQSB5A==";
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
.generatePrivate(new PKCS8EncodedKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(thekey)));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(getMaxResultDecrypt(password, cipher));
//log.info("RSA私钥解密后的数据|outStr:{}",outStr);
credentials.setPassword(outStr);

getMaxResultDecrypt方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static byte[] getMaxResultDecrypt(String str, Cipher cipher) throws Exception {  
byte[] inputArray = org.apache.commons.codec.binary.Base64.decodeBase64(str.getBytes("UTF-8"));
int inputLength = inputArray.length;
// 最大解密字节数,超出最大字节数需要分组加密
int MAX_ENCRYPT_BLOCK = 128;
// 标识
int offSet = 0;
byte[] resultBytes = {};
byte[] cache = {};
while (inputLength - offSet > 0) {
if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
offSet += MAX_ENCRYPT_BLOCK;
} else {
cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
offSet = inputLength;
} resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
} return resultBytes;
}

getMaxResultDecrypt方法是因为当解密字符较长的时候,解密会抛异常,提示Data must not be longer than 128 bytes