RSA算法简介及JAVA与Python加解密

1.RSA

RSA加密算法,它是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)共同提出的一种加密算法,RSA就是他们三人姓氏开头字母拼在一起组成的。
RSA算法是一种非对称加密算法,这一算法主要依靠分解大素数的复杂性来实现其安全性,由于大素数之积难被分解,因此该密码就难被破解。

1.1 RSA 原理

RSA基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。

1.2 加密与解密

非对称加密加密,最大的特点就是加密与解密使用的不是同一个秘钥。一般来说,秘钥在分发过程中,会有丢失的风险,这也就意味着别人可以获取秘钥,密文,因而可以解密得到明文。而非对称加密,公钥对所有人公开,使用公钥对明文加密得到密文。要想对密文进行解密,必须要使用私钥进行解密。私钥不需要发送给别人,只有解密者拥有。这样即使别人获取了密文,也没有私钥可以解密。降低了数据泄露的风险。

1.3 签名与验签

公钥私钥还有另外一种用途:签名。即用秘钥拥有者用私钥对数据进行签名,然后把数据发送出去,这时候所有人都可以获取到数据,但是数据是否被篡改了呢?拥有公钥的用户可以对私钥签名后的数据进行验证,这一步称为验签。如果签名验证通过,即可以认为数据是完整的。

1.4 公钥长度

我们经常会听到RSA-512, RSA-1024,这里的512,1024是什么呢?这里的1024指的是公钥的比特长度,即RSA-1024所生成的公钥长度为1024bit,即1024/8=128bytes。

秘钥的长度越长,安全性越好,加密与解密的所需要的时间也就越长。密钥长度增加一倍,公钥操作所需时间增加约 4 倍,私钥操作所需时间增加约 8 倍,公私钥生成时间约增长 16 倍。

RSA 算法密钥长度的选择是安全性和程序性能平衡的结果。不过也不要选择特别短的,首先JAVA要求不得低于512。其次,根据相关显示,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还未被破解,至少目前尚未有人公开宣布。(ps:一些非官方报道称,1024位的已经被解密)

1.5 加密明文长度

RSA算法一次能加密的明文长度与公钥长度相同,如RSA-1024算法一次可实际加密的最大明文长度为1024bits,生成的密文长度与公钥长度相同。但是如果明文长度小于1024bits怎么办?那就进行数据补齐,也就是padding。用padding,那么padding就会占据一定的位数,根据不同的padding标准,所占的位数也不同。常见的padding标准有NoPPadding、PKCS1Padding等。NoPPadding即意味着不占据长度,PKCS1Padding占据11个字节。一般情况下,我们会使用PKCS1Padding,这样可以让同样的明文使用相同的秘钥加密,也可以得到不同的密文。
由于PKCS1Padding占据11个字节,那么RSA-1024算法一次能加密的明文的实际长度就是1024/8-11=117字节。这也就是我们为什么会说RSA-1024一次只能加密117个字节的内容,超过117个字节的内容需要多次加密。然后将加密内容连接起来。
这样做得话解密怎么做呢?解密也要将拼接的字符串再拆开。由于一次生成的密文长度与公钥长度相同,因此可以轻而易举的将数据分段解密。

2. JAVA应用

由于为了方便,将密钥,密文等都用base64编码成字符串,这样方便copy等。

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RSAUtil extends AbstractCrypto {
    
    
    private static final String RSA_ALGORITHM = "RSA";
    private static final int RSA_2048 = 2048;

    public void generateSecret() throws Exception {
    
    
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        keyPairGenerator.initialize(RSA_2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();

        String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        System.out.println("public key string: " + publicKeyString);

        String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
        System.out.println("private key String: " + privateKeyString);
    }

    public String encrypt(String plaintext, String key) throws Exception {
    
    
        return publicKeyEncrypted(plaintext, key);
    }

    public String decrypt(String ciphertext, String key) throws Exception {
    
    
        return privateDecrypt(ciphertext, key);
    }

    public String publicKeyEncrypted(String plaintext, String publicKeyString) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException {
    
    
        RSAPublicKey publicKey = getPublicKey(publicKeyString);
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return Base64.getEncoder().encodeToString(rsaCodec(cipher, plaintext.getBytes(StandardCharsets.UTF_8), publicKey.getModulus().bitLength() / 8 - 11));
    }

    private RSAPublicKey getPublicKey(String publicKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException {
    
    
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        KeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyString));
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }

    private static RSAPrivateKey getPrivateKey(String privateKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException {
    
    
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        KeySpec pkcS8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyString));
        return (RSAPrivateKey) keyFactory.generatePrivate(pkcS8EncodedKeySpec);
    }

    public static String privateDecrypt(String ciphertext, String privateKeyString) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException {
    
    
        RSAPrivateKey privateKey = getPrivateKey(privateKeyString);

        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return new String(
                rsaCodec(cipher, Base64.getDecoder().decode(ciphertext), privateKey.getModulus().bitLength() / 8), StandardCharsets.UTF_8
        );
    }

    private static byte[] rsaCodec(Cipher cipher, byte[] data, int maxBlock) {
    
    
        int offset = 0;
        byte[] buffer;
        int i = 0;

        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
    
    
            while (data.length > offset) {
    
    
                if (data.length - offset > maxBlock) {
    
    
                    buffer = cipher.doFinal(data, offset, maxBlock);
                } else {
    
    
                    buffer = cipher.doFinal(data, offset, data.length - offset);
                }
                byteArrayOutputStream.write(buffer, 0, buffer.length);
                i++;
                offset = i * maxBlock;
            }
            return byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }
    }

}

3. Python应用

Python应用安装了第三方的依赖:

pip3 install pycryptodome

Python3代码如下

import Crypto.Util.number
from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
from Crypto.Cipher import AES
import base64
import os
import sys

encoding_utf8 = 'utf-8'
PRIVATE_KEY_BEGIN = '-----BEGIN PRIVATE KEY-----'
PRIVATE_KEY_END = '-----END PRIVATE KEY-----'
PUBLIC_KEY_BEGIN = '-----BEGIN PUBLIC KEY-----'
PUBLIC_KEY_END = '-----END PUBLIC KEY-----'


def rsa_create_key(bits):
    random_generator = Random.new().read
    rsa = RSA.generate(bits, random_generator)

    pkcs8_private_key = rsa.exportKey(format='PEM', passphrase=None, pkcs=8, protection=None)
    private_key_with_title_and_bottom = pkcs8_private_key.decode("utf-8")
    private_key_string = private_key_with_title_and_bottom.removeprefix(PRIVATE_KEY_BEGIN) \
        .removesuffix(PRIVATE_KEY_END).replace('\n', "")

    public_pem = rsa.publickey().exportKey()
    public_key_with_begin_and_end = public_pem.decode(encoding_utf8)
    public_key_string = public_key_with_begin_and_end.removeprefix(PUBLIC_KEY_BEGIN) \
        .removesuffix(PUBLIC_KEY_END).replace("\n", "")

    with open("private_key_string.pem", 'wb') as f:
        f.write(private_key_string.encode(encoding_utf8))

    return public_key_string, private_key_string


def rsa_public_key_encrypt(plaintext, public_key_string):
    rsa_key = RSA.importKey(base64.b64decode(public_key_string))
    cipher = Cipher_pkcs1_v1_5.new(rsa_key)

    max_block = int(Crypto.Util.number.size(rsa_key.n) / 8 - 11)
    length = len(plaintext)
    offset = 0
    res = []
    plaintext_bytes = plaintext.encode(encoding_utf8)
    while length - offset > 0:
        if length - offset > max_block:
            res.append(cipher.encrypt(plaintext_bytes[offset:offset + max_block]))
        else:
            res.append(cipher.encrypt(plaintext_bytes[offset:]))
        offset += max_block

    cipher_text = base64.b64encode(b''.join(res))
    return cipher_text.decode(encoding_utf8)

def rsa_private_key_decrypt(cipher_text):
    with open("private_key_string.pem", 'rb') as f:
        private_key_string = f.read()
    private_key = RSA.importKey(base64.b64decode(private_key_string))
    cipher = Cipher_pkcs1_v1_5.new(private_key)

    block_size = int(Crypto.Util.number.size(private_key.n) / 8)
    cipher_text_bytes = base64.b64decode(cipher_text)
    length = len(cipher_text_bytes)
    offset = 0
    res = []

    while length - offset > 0:
        if length - offset > block_size:
            res.append(cipher.decrypt(cipher_text_bytes[offset: offset + block_size], "ERROR"))
        else:
            res.append(cipher.decrypt(cipher_text_bytes[offset:], "ERROR"))
        offset += block_size

    plaintext = b''.join(res)
    return plaintext.decode(encoding_utf8)

4.总结

上述JAVA与Python代码,可以实现JAVA生成公钥密钥,JAVA用公钥解密,Python用私钥解密,反之亦可。
Python要注意设置私钥格式pkcs8,默认是pkcs1。为了和JAVA互用,这里设置格式为pkcs8。
另外,很多例子都是生成pem文件,这里我只是把文件里内容读写出来。

猜你喜欢

转载自blog.csdn.net/Apple_wolf/article/details/127912972