RSA算法系列一

RSA算法系列一

1 RSA基础

1.1 密钥生成的步骤如下

    1. 随意选择两个大的质数p和q,p不等于q,计算N=pq。
    2. 计算p和q的乘积n(将n转换为二进制后,二进制的长度就是密钥的长度,实际应用中一般选择1024位、2048位);
    3. 计算n的欧拉函数φ(n);
    4. 随机选择一个整数e,其中φ(n)>e>1,且e与φ(n)互质(实际应用中e一般选为65537);
    5. 计算e对于φ(n)的模反元素d;
    6. 将n和e封装成公钥,n和d封装成私钥。

1.2 生成方法(java版 jdk6以上提供了RSA算法的实现) 关键代码如下

 /** 
     * 初始化密钥 
     *  
     * @return 
     * @throws Exception 
     */  
    public static Map<String, Object> initKey() throws Exception {  
        KeyPairGenerator keyPairGen = KeyPairGenerator  
                .getInstance("RSA");  
        keyPairGen.initialize(1024);  

        KeyPair keyPair = keyPairGen.generateKeyPair();  

        // 公钥  
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  

        // 私钥  
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();  

        Map<String, Object> keyMap = new HashMap<String, Object>(2);  

        keyMap.put("RSAPublicKey", publicKey);  
        keyMap.put("RSAPrivateKey", privateKey);  
        return keyMap;  
    }  

1.3 RSA加解密代码如下

 /** 
     * 解密<br> 
     * 用私钥解密 
     *  
     * @param data 
     * @param key 
     * @return 
     * @throws Exception 
     */  
    public static byte[] decryptByPrivateKey(byte[] data, String key)  
            throws Exception {  
        // 对密钥解密  
        byte[] keyBytes = decryptBASE64(key);  
        // 取得私钥  
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);  

        // 对数据解密  
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  
        cipher.init(Cipher.DECRYPT_MODE, privateKey);  

        return cipher.doFinal(data);  
    }  
 /** 
     * 解密<br> 
     * 用公钥解密 
     *  
     * @param data 
     * @param key 
     * @return 
     * @throws Exception 
     */  
    public static byte[] decryptByPublicKey(byte[] data, String key)  
            throws Exception {  
        // 对密钥解密  
        byte[] keyBytes = decryptBASE64(key);  

        // 取得公钥  
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        Key publicKey = keyFactory.generatePublic(x509KeySpec);  

        // 对数据解密  
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  
        cipher.init(Cipher.DECRYPT_MODE, publicKey);  

        return cipher.doFinal(data);  
    }  
 /** 
     * 加密<br> 
     * 用公钥加密 
     *  
     * @param data 
     * @param key 
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptByPublicKey(byte[] data, String key)  
            throws Exception {  
        // 对公钥解密  
        byte[] keyBytes = decryptBASE64(key);  

        // 取得公钥  
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        Key publicKey = keyFactory.generatePublic(x509KeySpec);  

        // 对数据加密  
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);  

        return cipher.doFinal(data);  
    }  

    /** 
     * 加密<br> 
     * 用私钥加密 
     *  
     * @param data 
     * @param key 
     * @return 
     * @throws Exception 
     */  
    public static byte[] encryptByPrivateKey(byte[] data, String key)  
            throws Exception {  
        // 对密钥解密  
        byte[] keyBytes = decryptBASE64(key);  

        // 取得私钥  
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);  

        // 对数据加密  
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());  
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);  

        return cipher.doFinal(data);  
    }  
  /** 
     * 取得私钥 
     *  
     * @param keyMap 
     * @return 
     * @throws Exception 
     */  
    public static String getPrivateKey(Map<String, Object> keyMap)  
            throws Exception {  
        Key key = (Key) keyMap.get("RSAPrivateKey");  

        return encryptBASE64(key.getEncoded());  
    }  

    /** 
     * 取得公钥 
     *  
     * @param keyMap 
     * @return 
     * @throws Exception 
     */  
    public static String getPublicKey(Map<String, Object> keyMap)  
            throws Exception {  
        Key key = (Key) keyMap.get("RSAPublicKey");  

        return encryptBASE64(key.getEncoded());  
    }  
    /** 
     * BASE64 decrypt
     *  
     * @param key
     * @return 
     */
     public static byte[] decryptBASE64(String key) throws Exception {  
        return Base64.decodeBase64(key);  
     }  

    /** 
     * BASE64 encode
     *  
     * @param key
     * @return 
     */
    public static String encryptBASE64(byte[] key) throws Exception {  
         return Base64.encodeBase64String(key);  
   }  

1.4 RSA签名验签代码如下

 /** 
     * 用私钥对信息生成数字签名 
     *  
     * @param data 
     *            加密数据 
     * @param privateKey 
     *            私钥 
     *  
     * @return 
     * @throws Exception 
     */  
    public static String sign(byte[] data, String privateKey) throws Exception {  
        // 解密由base64编码的私钥  
        byte[] keyBytes = decryptBASE64(privateKey);  
        // 构造PKCS8EncodedKeySpec对象  
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);  
        // KEY_ALGORITHM 指定的加密算法  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        // 取私钥匙对象  
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);  

        // 用私钥对信息生成数字签名  
        Signature signature = Signature.getInstance("MD5withRSA");  
        signature.initSign(priKey);  
        signature.update(data);  

        return encryptBASE64(signature.sign());  
    }  
  /** 
     * 校验数字签名 
     *  
     * @param data 
     *            加密数据 
     * @param publicKey 
     *            公钥 
     * @param sign 
     *            数字签名 
     * @return 校验成功返回true 失败返回false 
     * @throws Exception 
     *  
     */  
    public static boolean verify(byte[] data, String publicKey, String sign)  
            throws Exception {  
        // 解密由base64编码的公钥  
        byte[] keyBytes = decryptBASE64(publicKey);  
        // 构造X509EncodedKeySpec对象  
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);  
        // KEY_ALGORITHM 指定的加密算法  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        // 取公钥匙对象  
        PublicKey pubKey = keyFactory.generatePublic(keySpec);  
        Signature signature = Signature.getInstance("MD5withRSA");  
        signature.initVerify(pubKey);  
        signature.update(data);  

        // 验证签名是否正常  
        return signature.verify(decryptBASE64(sign));  
    }  

2 RSA密钥长度、明文长度和密文长度

公钥指数e、私钥指数d和模值n。是关键

2.1 密钥长度

由于RSA密钥是(公钥+模值)、(私钥+模值)分组分发的,所以我们说的“密钥”指的是密钥对长度,一般只是指模值的位长度。目前主流可选值:1024、2048、3072、4096…

目前主流密钥长度至少都是二进制 1024bits以上,低于1024bit的密钥已经不建议使用(安全问题)。主流的模值是1024位,实际运算结果可能会略小于1024bits,注意,这个值不是绝对的,跟素数的生成算法有关系,只是告诉素数生成器“帮我生成一个接近1024位的素数而已”,真实的情况是素数生成器也只是在1024bits对应的整数附近进行“摸索”而已,没找到1024位,1023位也照样送出来。

公钥指数是随意选的,但目前行业上公钥指数普遍选的都是65537(0x10001,5bits),有一篇文章讲解为什么取这个数字 是除了1、3、5、17、257之外的最小素数,为什么不选的大一点?当然可以,只是考虑到既要满足相对安全、又想运算的快一点(加密时),PKCS#1的一个建议值而已。有意的把公钥指数选的小一点,但是对应私钥指数肯定很大,意图也很明确,大家都要用公钥加密,所以大家时间很宝贵,需要快一点。

公钥指数随意选,那么私钥就不能再随意选了,只能根据算法公式(ed%k=1,k=(p-1)(q-1))进行运算出来。那么私钥指数会是多少位?根据ed关系,私钥d=(x*k+1)/e,私钥指数似乎也不是唯一结果,可能大于也可能小于1024bits的,但我们习惯上也是指某个小于1024bits的大整数。包括前文的公钥指数,在实际运算和存储时为方便一般都是按照标准位长进行使用,前面不足部分补0填充,所以,使用保存和转换这些密钥需要注意统一缓冲区的长度。

2.2 明文长度

如果小于这个长度怎么办?就需要进行padding,因为如果没有padding,用户无法确分解密后内容的真实长度,字符串之类的内容问题还不大,以0作为结束符,但对二进制数据就很难理解,因为不确定后面的0是内容还是内容结束符。只有用到padding,那么就要占用实际的明文长度,于是才有117字节的说法。
我们一般使用的padding标准 NoPPadding、OAEPPadding、PKCS1Padding等,其中PKCS#1建议的padding就占用了11个字节。
如果大于这个长度怎么办?很多算法的padding往往是在后边的,但PKCS的padding则是在前面的,此为有意设计,有意的把第一个字节置0以确保m的值小于n。这样,128字节(1024bits)-减去11字节正好是117字节,但对于RSA加密来讲,padding也是参与加密的,所以,依然按照1024bits去理解,但实际的明文只有117字节了。

关于PKCS#1 padding规范可参考:RFC2313 chapter 8.1,我们在把明文送给RSA加密器前,要确认这个值是不是大于n,也就是如果接近n位长,那么需要先padding再分段加密。除非我们是“定长定量自己可控可理解”的加密不需要padding。

2.2 密文长度

密文长度就是给定符合条件的明文加密出来的结果位长,这个可以确定,加密后的密文位长跟密钥的位长度是相同的,因为加密公式:

C=(P^e)%n

所以,C最大值就是n-1,所以不可能超过n的位数。尽管可能小于n的位数,但从传输和存储角度,仍然是按照标准位长来进行的,所以,即使我们加密一字节的明文,运算出来的结果也要按照标准位长来使用。

RSA加密密文的长度不是固定的吗?

无论是加密或解密,运算的最后一步都是对N取模,所以结果的位数一定小于、等于N的位数。一个256位的N,结果为255位或256位都很正常。计算出来的,没法固定为256位!
这个与PADDING完全不相干。
为什么需要PADDING?
因为明文信息的长度有可能与“模”不同:如果明文位数小于“模”的长度,那么解密出来后怎么还原出原来的明文长度、有用的信息?需要某种规则来定义,这就是PADDING。
如果明文长度大于N,则需要按“模”的长度“分组”,最后一组可能同样需要PADDING。
“补足”可以自己定义,但加密和解密必须遵守相同的PADDING“协议”,否则就会解密不出原文。已经有好几种符合工业标准的PADDING规范,比如PKCS1.5:


[FONT="Courier"]/*
   RFC 2313 - PKCS #1: RSA Encryption Version 1.5
   https://tools.ietf.org/html/rfc2313
   EB = 00 || BT || PS || 00 || D
        EB - Encryption Block
        BT - Block Type                 :: 00 or 01 for private-key operation; 02 for public-key operation
        PS - Padding String             :: length = k-3-||D||
                                           BT 00: all 00
                                           BT 01: all FF
                                           BT 02: pseudorandomly generated and nonzero
         D - Data
         k - length of modulus in octets
*/[/FONT]

请注意填充块类型(Block Type)为02时,使用了伪随机数进行填充,所以相同的输入,每次加密结果都会不一样。
在私钥的情况下,通常用于服务端,出于性能考虑,用固定字节填充BT 00/01。
在公钥时,用于客户端,用伪随机数填充BT 02。

参考文章

http://www.metsky.com/archives/657.html
http://snowolf.iteye.com/blog/381767
https://bbs.pediy.com/thread-213463.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值