RSA加密,开发必懂的开发常识

数据通信 系列目录

  1. RSA加密,你需要掌握的知识
  2. 网络通信之——Https数字证书
  3. 网络通信之——SSL与TLS
  4. 数字证书中签名算法、签名哈希算法区别?

博客创建时间:2020.05.09
博客更新时间:2021.02.23


前言

Https通信的数字证书中一般采用RSA(公钥密码体制)。RSA是一种广泛应用的加密方法。


加密科普

下面我们对几个知识点进行科普

数据加解密
加密就是是指对某个内容经过一定的加密算法及秘钥转化成base64、hex等格式。解密就是通过特定的解密算法及秘钥将加密信息转换成原始信息。数据的加解密是为了数据传输的安全,防止被他人截取。加解密算法一般就是对称加密和非对称加密。

秘钥
密钥,一般就是一个字符串或数字,在加密或者解密时作为参数传递给加密/解密算法。加解密双方可以自行约定加解密方法,所以就出现了多种加解密方法。

公钥密码体制(public-key cryptography)

公钥密码体制分为三个部分,公钥、私钥、加密解密算法,它的加密解密过程如下:

  1. 加密:通过加密算法和公钥对内容(或者说明文)进行加密,得到密文。加密过程需要用到公钥。
  2. 解密:通过解密算法和私钥对密文进行解密,得到明文。解密过程需要用到解密算法和私钥。

公钥密码体制的公钥和算法都是公开的(这是为什么叫公钥密码体制的原因),私钥是保密的。两者都可以加解密数据,公钥加密的内容只能私钥解密,私钥加密的内容只能公钥解密

数据签名
签名就是在信息的后面再加上一段内容,可以证明信息没有被修改过。签名一般使用的方案:

  1. 是对信息做一个hash计算得到一个hash值,然后把这个hash值(加密后)作为一个签名和信息一起发出去。Hash值需要进行加密是为了防止有人修改hash值。
  2. 接收方在收到信息后,会重新计算信息的hash值,并和信息所附带的hash值(解密后)进行对比,如果一致,就说明信息的内容没有被修改过。
    1. 这个过程是不可逆的,也就是说无法通过hash值得出原来的信息内容。
    2.不同的内容一定会得到不同的hash值,hash的加解密是为了防止传输过程中被更改,造成信息是否被篡改无法准确验证。

对称加密算法(symmetric key algorithms)
对称加密算法就是加密使用的密钥和解密使用的密钥是相同的。因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道。安全系数相对较低


非对称加密算法(asymmetric key algorithms)
非对称加密算法就是加密使用的密钥和解密使用的密钥是不相同的。RSA就是一种非对称加密算法。


RSA加密

RSA采用的就是一种公钥密码体制。RSA是三位数学家Rivest、Shamir 和 Adleman 设计的一种算法,所以叫做RSA。它是计算机通信安全的基石,也是最重要的加密算法。

这种算法非常可靠,密钥越长,它就越难破解。根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全(网络通信中一般都是2048位的)。

对于RSA的算法原理推荐查看博客文章《RSA算法原理》

RSA通信示例
在实际应用中我们以Https通信为例来分析RSA的使用过程,现在模拟https通信中client和server的常见对话:

  • client >>server:你好,我是clientA
  • server>>client:你好,我是server。
  • client >>server:请证明你是Server,str [str是随机字符串]
  • server>>client:str{XXX-hash} [ {}中是私钥RSA加密后内容,hash为str的has,后面都如此表示]
    [client 收到str原文,计算str的hash1,通过证书中RSA公钥解密XXX-hash,获得密文中的内容与hash2,比较解密内容与原内容的hash值是否一致,解密的hash和原文计算的hash是否一致,都一致说明服务器是真的,可以进行下一步]
  • client >>server:你确实是server,这是x加密算法的秘钥{XXXX},以后就用X加密算法通信吧
  • server>>client:{可以,我做好通信准备了}[内容经x算法加密]
  • client >>server:{查询一下我的账户余额}[内容经x算法加密]

**注意**
1.验证双方身份的真实性后,双方可以约定使用新的对称加密算法通信,这个比较自由,扩张性很强,可能不同的client与server的通信加密方法是不一样的。
2.在验证双方身份后,也可以client将自己生成的非对称公钥传递给server,通过非对称加密进行双方的通信。

通信前提:client已经拿到了server的数字证书。证书中包含有服务器RSA公钥。关于数字证书原理和其他信息,请阅读该篇博客《Https通信之数字证书》

一般我们开发应用,第一次是在程序中放了数字证书的,在快过期时,可以通过server下载新的证书进行保存并投入使用。


其他注意

1. 为什么要使用RSA加解密通信内容?
答:为了数据安全,client与server直接的通信在多个层面都能被黑客攻击,通过伪装成server或者client进行数据的窃取和数据篡改。

2. 为什么不在第一次通话中获取server的证书?或者放在某个网站下载?
答:你一开始访问的网站未必是真的网站,下载或传输的证书也可以是假的证书。

3. 为什么要对通信内容同时使用加密和签名?
答:虽然“黑客”无法获知加密内容,但是可以修改加密内容,如给它首位加一段内容、信息部分内容被替换,进行信息干扰。信息通过加密和内容hash值得签名,即可判断通信内容是否被修改破坏,是否完整。

4. client验证server签名为什么是通过随机字符串的hash,而不是直接通过加解密的方式?
答:因为”黑客”可以通过发送简单有规律的字符串,如”0,1,2,3,4”等找到加密规律,破解加密方法,这样是不安全的。通过对字符串的hash值进行加密,client收到内容后,解密字符串的hash值并与传过来的字符串计算出来的hash进行比较,即可验证server的签名。

5. 如何解决”黑客”截取加密内容,多次重复发送信息?
答:给通信内容添加序号和随机号,client或者server收到同样的信息,则说明通信有被干扰,应停止通信进行其他处理。

RSA工具Java类

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;
/**
 * 时间:2020/4/20 0020    星期一
 * 邮件:424533553@qq.com
 * 说明:RSA非对称加密
 */
class RSAUtils {
    private final String CHARSET = "UTF-8";
    private final String RSA_ALGORITHM = "RSA";

    private Map<String, String> createKeys(int keySize) {
        //为RSA算法创建一个KeyPairGenerator对象
        KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
        }

        //初始化KeyPairGenerator对象,密钥长度
        kpg.initialize(keySize);
        //生成密匙对
        KeyPair keyPair = kpg.generateKeyPair();
        //得到公钥
        Key publicKey = keyPair.getPublic();
        String publicKeyStr = Base64.getEncoder().encodeToString(publicKey.getEncoded());

        //得到私钥
        Key privateKey = keyPair.getPrivate();
        String privateKeyStr = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKey", publicKeyStr);
        keyPairMap.put("privateKey", privateKeyStr);
        return keyPairMap;
    }

    /**
     * 得到公钥
     *
     * @param publicKey 密钥字符串(经过base64编码)
     */
    private RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通过X509编码的Key指令获得公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
        return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
    }

    /**
     * 得到私钥
     * @param privateKey 密钥字符串(经过base64编码)
     */
    private RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //通过PKCS#8编码的Key指令获得私钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
        return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
    }

    /**
     * 公钥加密
     */
    private String publicEncrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength());
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥解密
     *
     * @param data    待解密数据
     * @param privateKey    私钥
     */
    private String privateDecrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] bytes = Base64.getDecoder().decode(data);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, bytes, privateKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 私钥加密
     *
     */
    public String privateEncrypt(String data, RSAPrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] bytes = rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength());
            return Base64.getEncoder().encodeToString(bytes);
        } catch (Exception e) {
            throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
        }
    }

    /**
     * 公钥解密
     */
    public String publicDecrypt(String data, RSAPublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] bytes = Base64.getDecoder().decode(data);
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, bytes, publicKey.getModulus().bitLength()), CHARSET);
        } catch (Exception e) {
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
        }
    }

    private byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
        int maxBlock;
        if (opmode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] buff;
        int i = 0;
        try {
            while (datas.length > offSet) {
                if (datas.length - offSet > maxBlock) {
                    buff = cipher.doFinal(datas, offSet, maxBlock);
                } else {
                    buff = cipher.doFinal(datas, offSet, datas.length - offSet);
                }
                out.write(buff, 0, buff.length);
                i++;
                offSet = i * maxBlock;
            }
        } catch (Exception e) {
            throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
        }
        byte[] resultDatas = out.toByteArray();
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resultDatas;
    }

    public static void main(String[] args) throws Exception {
        RSAUtils rsaUtils = new RSAUtils();
        Map<String, String> keyMap = rsaUtils.createKeys(1024);
        String publicKey = keyMap.get("publicKey");
        String privateKey = keyMap.get("privateKey");
        System.out.println("公钥: \n\r" + publicKey);
        System.out.println("私钥: \n\r" + privateKey);

        System.out.println("公钥加密——私钥解密");
        String str = "孙子,我是你爸爸";
        System.out.println("\r明文:\r\n" + str);
        System.out.println("\r明文大小:\r\n" + str.getBytes().length);
        String encodedData = rsaUtils.publicEncrypt(str, rsaUtils.getPublicKey(publicKey));
        System.out.println("密文:\r\n" + encodedData);
        String decodedData = rsaUtils.privateDecrypt(encodedData, rsaUtils.getPrivateKey(privateKey));
        System.out.println("解密后文字: \r\n" + decodedData);
    }
}


总结

1.信息在通信中加密可看为是防止数据泄漏被解密窃取
2.数据签名是为了判断收到的数据是否完整,保证client和server能收到完整、无关方无法解密加密数据,才是网络通信数据安全的核心点。


相关链接

  1. 网络通信之——RSA加密签名
  2. 网络通信之——Https数字证书
  3. 网络通信之——SSL与TLS
  4. 数字证书中签名算法、签名哈希算法区别?

扩展链接:

  1. Android CameraX 使用入门
  2. Android Studio 4.0新特性及升级异常

扩展训练:

  1. 了解加密算法的概念(尤其是“对称加密与非对称加密”的区别)

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值