RSA加密算法,它是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)共同提出的一种加密算法,RSA就是他们三人姓氏开头字母拼在一起组成的。
RSA算法是一种非对称加密算法,这一算法主要依靠分解大素数的复杂性来实现其安全性,由于大素数之积难被分解,因此该密码就难被破解。
RSA基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。
非对称加密加密,最大的特点就是加密与解密使用的不是同一个秘钥。一般来说,秘钥在分发过程中,会有丢失的风险,这也就意味着别人可以获取秘钥,密文,因而可以解密得到明文。而非对称加密,公钥对所有人公开,使用公钥对明文加密得到密文。要想对密文进行解密,必须要使用私钥进行解密。私钥不需要发送给别人,只有解密者拥有。这样即使别人获取了密文,也没有私钥可以解密。降低了数据泄露的风险。
公钥私钥还有另外一种用途:签名。即用秘钥拥有者用私钥对数据进行签名,然后把数据发送出去,这时候所有人都可以获取到数据,但是数据是否被篡改了呢?拥有公钥的用户可以对私钥签名后的数据进行验证,这一步称为验签。如果签名验证通过,即可以认为数据是完整的。
我们经常会听到RSA-512, RSA-1024,这里的512,1024是什么呢?这里的1024指的是公钥的比特长度,即RSA-1024所生成的公钥长度为1024bit,即1024/8=128bytes。
秘钥的长度越长,安全性越好,加密与解密的所需要的时间也就越长。密钥长度增加一倍,公钥操作所需时间增加约 4 倍,私钥操作所需时间增加约 8 倍,公私钥生成时间约增长 16 倍。
RSA 算法密钥长度的选择是安全性和程序性能平衡的结果。不过也不要选择特别短的,首先JAVA要求不得低于512。其次,根据相关显示,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还未被破解,至少目前尚未有人公开宣布。(ps:一些非官方报道称,1024位的已经被解密)
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个字节的内容需要多次加密。然后将加密内容连接起来。
这样做得话解密怎么做呢?解密也要将拼接的字符串再拆开。由于一次生成的密文长度与公钥长度相同,因此可以轻而易举的将数据分段解密。
由于为了方便,将密钥,密文等都用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);
}
}
}
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)
上述JAVA与Python代码,可以实现JAVA生成公钥密钥,JAVA用公钥解密,Python用私钥解密,反之亦可。
Python要注意设置私钥格式pkcs8,默认是pkcs1。为了和JAVA互用,这里设置格式为pkcs8。
另外,很多例子都是生成pem文件,这里我只是把文件里内容读写出来。