Java安全系列-RSA加密

Posted Zip Zou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java安全系列-RSA加密相关的知识,希望对你有一定的参考价值。

Java安全系列-RSA加密

在现在的信息安全体系中,加密已经成为了一个常识。在诸多场合下为了数据安全和可靠,很多情况下需要使用加密,如:密码体系、安全邮件、网络传输等等方面。

在现有的加密算法中,大致可以分为两大类:对称加密和非对称加密。

在对称加密中,较为典型的算法为:DES、IDEA、AES等;

在非对称加密体系中,其中较为常用的算法有:RSA加密体系,ECC加密等。

在上述的加密过程中,都需要借助密钥。在对称加密算法体系中,其加密解密,需要使用相同的密钥进行加密和解密。在该过程中,产生密钥E,同时有明文数据P,为此采用相应的对称算法对其进行加密,会得到密文C。该过程则为对称加密过程。在加密后,若需要对数据进行解密,则同样需要使用密钥E,对密文C进行解密,得到明文P,该过程为解密过程。

在对称加密中,有:加密速度快、计算量小等优点,并可公开算法,只需做到密钥的管理,即可保证加密的安全性。但是在对称加密体系中,密钥是多种多样的,并且为了保证数据可靠,必须做到严格的密钥管理,会对用户造成负担,从而造成数据的不安全性。

在非对称加密体系中,需要协商产生密钥对:私钥-公钥,产生一对密钥后,公钥可以公开使用,交由客户端或用户使用,令客户在加密时,采用公钥进行加密,产生密文,交由私钥拥有者进行使用。拥有私钥的一方,接收到经过公钥加密的密文后,使用只有其保留的私钥对密文进行解密,从而可以得到明文。该过程则为普遍的“公钥加密,私钥解密”的过程体系。并且在非对称加密中,可以使用“私钥加密,公钥解密”的过程,来实现数字签名,因为私钥加密,可以实现不可否认性。由于在非对称加密体系中,只有一部分有权限的用户,可以保留私钥,其他用户只能保留公钥,公钥可以公开在网络上进行传输。因此用户通过使用私钥对数据进行加密,则一定能够保证该数据是由私钥持有者进行加密(签名),其他用户在接收到密文时,采用公钥进行解密,则一定能够确认信息发送者是唯一的。

RSA数字签名算法的过程为:A对明文m用解密变换作: s Dk (m)=md mod n,其中d,n为A的私人密钥,只有A才知道它;B收到A的签名后,用A的公钥和加密变换得到明文,因: Ek(s)= Ek(Dk (m))= (md)e mod n,又 de1 mod (n)即de=l(n)+1,根据欧拉定理m(n)=1 mod n,所以Ek(s)=ml(n)+1=[m(n)]em=m mod n.若明文m和签名s一起送给用户B,B可以确信信息确实是A发送的.同时A也不能否认送给这个信息,因为除了A本人外,其他任何人都无法由明文m产生s.因此RSA数字签名方案是可行的. —-引用至RSA算法和RSA数字签名算法的实现

在非对称加密体系中,其理论支撑为“大数分解“的数学难题,若数足够大,我们可以认定,该难题是无解的,从而保证加密的安全性。

其中在Linux的SSH连接中,可以配置免密钥登录,实际上,依然采用的是非对称加密体系,对连接进行加密。如以下过程:

  1. A主机希望通过SSH免密钥连接到B主机;
  2. 在A主机中,产生密钥对,E1(私钥)、E2(公钥);
  3. 将E2分发到B主机中;
  4. A主机连接B主机时,A主机发送连接请求到B主机,其中携带信息包括:IP地址、用户名等;
  5. B主机接收到数据后,查询已知主机列表中是否有该IP地址和用户,若没有则提示用户是否要添加到已知列表。若有,则B主机会随机产生一个字符串S,用于身份认证,该字符串S使用E2公钥进行加密发送到主机A中;
  6. 主机A接受到加密后的随机字符串,主机A使用其私钥对其进行解密,得到明文数据P,发送到主机B中;
  7. B接受该明文数据,对明文进行判断,若和产生的随机数一致,则连接成功;

图引用至:ssh免密码登录的原理

其他加密算法,如:MD5、SHA-1、SHA2等,则为Hash算法,该过程一般来说,是不可逆的。

Java实现RSA加密

准备工作:借助apache commons codec工具进行Base64编解码,实现密钥的编解码。

在实现RSA时,通常分为2大步:

  1. 协商密钥对,并产生一对密钥;
  2. 利用公钥进行加密,利用私钥进行解密。

产生密钥可以使用如下代码:


package site.franksite.encrpt.rsaencrypt;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Date;

import org.apache.commons.codec.binary.Base64;

/**
 * RSA密钥生成类
 * @author frank
 *
 */
public class RSAKeyGenerator 

    private byte[] publicKeyEncoded;
    private byte[] privateKeyEncoded;

    private static final int KEY_LENGTH = 1024;

    public void generate() 
        try 
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            SecureRandom secRandom = new SecureRandom();
            secRandom.setSeed(new Date().getTime());

            generator.initialize(KEY_LENGTH, secRandom);

            // 产生键值对
            KeyPair pair = generator.generateKeyPair();

            PublicKey pubKey = pair.getPublic();
            PrivateKey priKey = pair.getPrivate();

            // 加密公钥私钥
            publicKeyEncoded = Base64.encodeBase64(pubKey.getEncoded());
            privateKeyEncoded = Base64.encodeBase64(priKey.getEncoded());

         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
        
    

    /**
     * @return the publicKeyEncoded
     */
    public byte[] getPublicKeyEncoded() 

        if (null == publicKeyEncoded) 
            generate();
        

        return publicKeyEncoded;
    

    /**
     * @return the privateKeyEncoded, encrypt by Base64
     */
    public byte[] getPrivateKeyEncoded() 
        if (null == privateKeyEncoded) 
            generate();
        
        return privateKeyEncoded;
    

在获得到密钥对时,我们需要将其保存,或持久化到数据库,或持久化到文件,供加密解密过程中使用。

在加密时,可以如下实现:

public byte[] encrypt(byte[] pubKey, byte[] data) 
        PublicKey key = restorePublicKey(pubKey);

        try 
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return cipher.doFinal(data);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
         catch (NoSuchPaddingException e) 
            e.printStackTrace();
         catch (InvalidKeyException e) 
            e.printStackTrace();
         catch (IllegalBlockSizeException e) 
            e.printStackTrace();
         catch (BadPaddingException e) 
            e.printStackTrace();
        

        return null;
    

在该代码中,首先将公钥字节码,还原为公钥实例,再调用Cipher对数据进行加密。

其中还原公钥代码为:

    /**
     * 将公钥字节码转为公钥实例
     * @param publicKey RSA公钥字节码
     * @return 公钥实例
     */
    public PublicKey restorePublicKey(byte[] publicKey) 
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        try 
            KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPT_ALGORITHM);
            PublicKey pubKey = keyFactory.generatePublic(keySpec);
            return pubKey;
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
         catch (InvalidKeySpecException e) 
            e.printStackTrace();
        

        return null;
    

加密后,密文将作为字节流的方式返回,我们可以对该二进制字节码文件采用Base64进行编码,产生Base64字符串,进行存储或转发。

同样的解密过程也分为上述过程,首先还原私钥的二进制流数据:

    /**
     * 将私钥字节码转为私钥实例
     * @param pivateKey RSA私钥字节码
     * @return 私钥实例
     */
    public PrivateKey restorePrivateKey(byte[] pivateKey) 

        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pivateKey);

        try 
            KeyFactory keyFactory = KeyFactory.getInstance(ENCRYPT_ALGORITHM);
            PrivateKey key = keyFactory.generatePrivate(keySpec);
            return key;
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
         catch (InvalidKeySpecException e) 
            e.printStackTrace();
        

        return null;
    

在获得到该私钥实例后,使用该私钥进行解密:

public byte[] dencrypt(byte[] priKey, byte[] data) 
        // 还原私钥
        PrivateKey privateKey = restorePrivateKey(priKey);

        try 
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
         catch (NoSuchPaddingException e) 
            e.printStackTrace();
         catch (InvalidKeyException e) 
            e.printStackTrace();
         catch (IllegalBlockSizeException e) 
            e.printStackTrace();
         catch (BadPaddingException e) 
            e.printStackTrace();
        

        return null;

解密后的数据将以字节流的形式返回!

注:上述代码中的常量CIPHER_ALGORITHM值为:”RSA/ECB/PKCS1Padding”,ENCRYPT_ALGORITHM为:”RSA”

通过上述方式,即可完成加密及解密。

同样的,采用类似的过程,可以借助RSA实现数字签名:

public byte[] sign(byte[] priKey, byte[] data) 
        PrivateKey privateKey = restorePrivateKey(priKey);

        try 
            Signature sign = Signature.getInstance("MD5withRSA");
            sign.initSign(privateKey);
            sign.update(data);
            return Base64.encodeBase64(sign.sign());
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
         catch (InvalidKeyException e) 
            e.printStackTrace();
         catch (SignatureException e) 
            e.printStackTrace();
        

        return null;

上述为签名过程。

public boolean verify(byte[] pubKey, byte[] data, byte[] originData) 
        PublicKey publicKey = restorePublicKey(pubKey);
        Signature sign;
        try 
            sign = Signature.getInstance("MD5withRSA");
            sign.initVerify(publicKey);
            sign.update(originData);
            return sign.verify(data);
         catch (NoSuchAlgorithmException e) 
            e.printStackTrace();
         catch (InvalidKeyException e) 
            e.printStackTrace();
         catch (SignatureException e) 
            e.printStackTrace();
        
        return false;

上述为签名验证过程。

项目下载

本项目实现了JAVA RSA加密及数字签名,有需要的用户可以到github检出或浏览:JAVA RSAEncrypt

以上是关于Java安全系列-RSA加密的主要内容,如果未能解决你的问题,请参考以下文章

Java安全系列-RSA登录表单加密

Java安全系列-RSA登录表单加密

RSA加密解密及RSA加签验签

怎么在ios进行rsa公钥加密,java做rsa私钥解密

.NET Core加解密实战系列之——RSA非对称加密算法

RSA加密、解密、签名、验签的原理及方法