openssl在多平台和多语言之间进行RSA加解密注意事项

时间:2021-04-12 21:38:33

首先说一下平台和语言:

系统平台为CentOS6.3,RSA加解密时使用NOPADDING进行填充

1)使用C/C++调用系统自带的openssl

2)Android4.2模拟器,第三方openssl(android-external-openssl-master),使用ndk编译静态库,然后使用C/C++进行调用

3)使用Java自身的类库(javax.crypto和java.security)

在linux下,使用如下命令,生成RSA加解密时使用的public和private的key

openssl genrsa -out rsa_private_key.pem 1024  //生成私钥

openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout   //生成公钥

openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt  //转化成PKCS#8编码编码,供Java解密时,进行调用

假如我们要对字符串"12345678”进行加解密

1)C/C++调用系统自带的openssl加解密

//============================================================================
// Name : HelloPlus.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================ #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//#include <iostream>
#include <sys/types.h>
#include <string.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h> #define PUBSSLKEY "rsa_public_key.pem"
#define PRISSLKEY "rsa_private_key.pem"
#define BUFFSIZE 1024
#define MAX_PATH 256 pid_t get_pid_from_proc_self();
char * get_self_executable_directory();
unsigned char *my_encrypt(unsigned char *str,char *pubPath,char *priPath);
unsigned char *byteArrayToString(unsigned char *data,int dataLen); //using namespace std; unsigned char *encode64(unsigned char *data,int dataLen)
{
/*
char base64code[] = {
'A','B','C','D','E','F','G','H','I','J','K','L','M','N'\
,'O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b'\
,'c','d','e','f','g','h','i','j','k','l','m','n','o','p'\
,'q','r','s','t','u','v','w','x','y','z','0','1','2','3'\
,'4','5','6','7','8','9','+','/'};
*/
//char base64code[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmlopqrstuvwxyz0123456789+/";
unsigned char *base64code = (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmlopqrstuvwxyz0123456789+/";
int times = dataLen%3;
int page = dataLen/3; unsigned char *outData = (unsigned char*)malloc(4 * (page + 1) + 1);
memset(outData,0,4 * (page + 1) + 1); unsigned char buff[3];
unsigned char instr[4] = {0};
unsigned char *ptr = data;
unsigned char *outPtr = outData;
for(int i = 0; i < page; i++)
{
memset(buff,0,3);
memcpy(buff,ptr,3);
ptr += 3; instr[0] = buff[0] >> 2;
instr[1] = (buff[0] & 0x03) << 4 | buff[1] >> 4;
instr[2] = (buff[1] & 0x0f << 2) | buff[2] >> 6;
instr[3] = buff[2] & 0x3f; *outPtr++ = base64code[instr[0]];
*outPtr++ = base64code[instr[1]];
*outPtr++ = base64code[instr[2]];
*outPtr++ = base64code[instr[3]];
}
int mod = dataLen%3;
if(mod == 1)
{
buff[0] = *ptr;
*outPtr++ = base64code[buff[0] >> 2];
*outPtr++ = base64code[(buff[0] & 0x03) << 4 ];
*outPtr++ = '=';
*outPtr++ = '=';
}
else if(mod == 2)
{
buff[0] = *ptr++;
buff[1] = *ptr;
*outPtr++ = base64code[buff[0] >> 2];
*outPtr++ = base64code[((buff[0] & 0x03) << 4) | (buff[1] >> 4)];
*outPtr++ = base64code[(buff[1] & 0x0f) << 2];
*outPtr++ = '=';
}
return outData;
} unsigned char *char2hex(unsigned char *data,int dataLen)
{
unsigned char *tmp = (unsigned char*)malloc(dataLen * 2 + 1);
memset(tmp,0,dataLen * 2 + 1);
char *ptr = (char*)tmp;
for(int i = 0; i < dataLen; i++)
{
sprintf(ptr,"%02X",data[i]);
ptr += 2;
}
return tmp;
} unsigned char *my_encrypt(unsigned char *str,char *pubPath,char *priPath)
{
unsigned char *p_en;
RSA *p_rsa;
FILE *file;
int flen,rsa_len; if((file = fopen(pubPath,"r")) == NULL) {
perror("open pub key file error");
return NULL;
} /*
BIO *key = NULL;
key = BIO_new(BIO_s_file());
BIO_read_filename(key,pubPath);
if((p_rsa = PEM_read_bio_RSA_PUBKEY(key,NULL,NULL,NULL)) == NULL) {
//if((p_rsa = PEM_read_bio_RSAPrivateKey(key,NULL,NULL,NULL)) == NULL) {
ERR_print_errors_fp(stdout);
BIO_free_all(key);
return NULL;
}
*/ if((p_rsa = PEM_read_RSA_PUBKEY(file,NULL,NULL,NULL)) == NULL) {
ERR_print_errors_fp(stdout);
return NULL;
} /*
BIO *bio;
X509 *centificate;
bio = BIO_new(BIO_s_file());
BIO_read_filename(bio,keyPath);
centificate = PEM_read_bio_X509(bio,NULL,NULL,NULL);
EVP_PKEY *pubKey = X509_get_pubkey(centificate);
RSA *rsa_pkey = EVP_PKEY_get1_RSA(pubKey); if(!PEM_read_RSAPublicKey(file,&rsa_pkey,NULL,NULL))
{
ERR_print_errors_fp(stdout);
return NULL;
}
*/ flen = strlen((char*)str);
rsa_len = RSA_size(p_rsa);
p_en = (unsigned char *)malloc(rsa_len + 1);
memset(p_en,0,rsa_len + 1);
int iret = RSA_public_encrypt(rsa_len,str,p_en,p_rsa,RSA_NO_PADDING);
if(iret < 0)
{
return NULL;
}
RSA_free(p_rsa);
fclose(file); if((file = fopen(priPath,"r")) == NULL) {
perror("open key file error");
return NULL;
} if((p_rsa = PEM_read_RSAPrivateKey(file,NULL,NULL,NULL)) == NULL) {
ERR_print_errors_fp(stdout);
return NULL;
} unsigned char p_de[1024] = {0};
iret = RSA_private_decrypt(iret,p_en,p_de,p_rsa,RSA_NO_PADDING);
if(iret < 0)
return NULL; printf("content is [%s]\n",p_de);
RSA_free(p_rsa);
fclose(file); return p_en;
} pid_t get_pid_from_proc_self()
{
char target[32];
int pid;
readlink("/proc/self",target,sizeof(target));
sscanf(target,"%d",&pid);
return pid;
} char * get_self_executable_directory()
{
int rval;
char link_target[1024];
char *last_slash;
size_t result_length;
char *result; rval =readlink("/proc/self/exe",link_target,sizeof(link_target));
if(rval == -1)
abort();
else
link_target[rval] = '\0'; last_slash = strrchr(link_target,'/');
if(last_slash == NULL || last_slash == link_target)
abort(); result_length = last_slash-link_target;
result = (char*)malloc(result_length + 1);
strncpy(result,link_target,result_length);
result[result_length] = '\0';
return result;
} unsigned char *byteArrayToString(unsigned char *data,int dataLen) {
char hex_char[] = "0123456789abcdef"; unsigned char *output = (unsigned char*)malloc(dataLen * 2 + 1);
bzero(output,dataLen * 2 + 1);
unsigned char *ptr = output;
for(int i = 0; i < dataLen; i++) {
*ptr++ = hex_char[(data[i] & 0xf0) >> 4];
*ptr++ = hex_char[(data[i] & 0x0f)];
}
return output;
} int main() {
/*
printf("/proc/self reports process id %d\n",get_pid_from_proc_self());
printf("getpid() reports process id %d\n",getpid());
printf("current program path is %s\n",get_self_executable_directory());
*/ char *msg = (char*)"12345678";
unsigned char *buf = (unsigned char *)malloc(128);
memset(buf,0,128);
memcpy(buf,msg,strlen(msg));
char pubPath[MAX_PATH] = {0};
char priPath[MAX_PATH] = {0};
char *executePath = get_self_executable_directory();
sprintf(pubPath,"%s/%s",executePath,PUBSSLKEY);
sprintf(priPath,"%s/%s",executePath,PRISSLKEY); printf("rsa pub key path is [%s]\n",pubPath);
printf("rsa pri key path is [%s]\n",priPath); unsigned char *ptr_en = my_encrypt(buf,pubPath,priPath);
//char *data = char2hex((unsigned char*)ptr_en,strlen(ptr_en));
char *base64Data = (char*)encode64(ptr_en,128);
printf("encrypt base64 data is [%s]\n",base64Data); char *hexData = (char*)byteArrayToString(ptr_en,128);
printf("encrypt hex data is [%s]\n",hexData);
return 0;
}

  

2)Android4.2下,使用的加解密代码与之相同,只是编译时需要使用第三方openssl,然后使用ndk-build进行编译,编译成功后,使用如下命令上传到模拟器中:

使用命令 "emulator -avd android4_2"或"emulator-arm -avd android4_2",启动模拟器,我的模拟器是android4_2,如果你的和我不一样,换成你的名称。

使用adb shell登陆模拟器,在data目录下创建文件夹RSATest,将编译成功的可执行文件和rsa_public_key.pem和rsa_private_key.pem上传到该目录

3)Java使用的RSA加解密代码如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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 javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import sun.misc.BASE64Decoder; public class RSAEncrypt { private static final String DEFAULT_PUBLIC_KEY=
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrUUwBjj0lVWubst1p49N9Y9ti" + "\r" +
"Xh/L4SH5TneNCr1WZHKXDJJM7sLV071UgTl3ENfrnsndNKPXgDDoMBuNnwhCzKHJ" + "\r" +
"Hu+stxrCWBUKfF/1NawwgBdxz+HIIcwyMVfWGDvc9KSSUXVwTg9frgj9i1FF3TUB" + "\r" +
"66qVIx3fNOwrjlz+0QIDAQAB" + "\r"; private static final String DEFAULT_PRIVATE_KEY=
"MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOtRTAGOPSVVa5uy" + "\r" +
"3Wnj031j22JeH8vhIflOd40KvVZkcpcMkkzuwtXTvVSBOXcQ1+ueyd00o9eAMOgw" + "\r" +
"G42fCELMocke76y3GsJYFQp8X/U1rDCAF3HP4cghzDIxV9YYO9z0pJJRdXBOD1+u" + "\r" +
"CP2LUUXdNQHrqpUjHd807CuOXP7RAgMBAAECgYBlwRK/vXT9VtGgUxjhOA30s6Bj" + "\r" +
"CdZv/9sEBgU2LQWwfOD8JgiBUeFYOyYsi3CA5vynO1OI3sFWZ20+icbwV2tnOt4w" + "\r" +
"A0kevQsnTTgR3RzutqPdMxJT7HseukGCrLcq0FK2UxZwOxFA9ACGQB70YlYk7sbX" + "\r" +
"p4xYMJXXiz7KIVvuXQJBAPz88QLAkdcxZC/Nmbfvwv/DuyYkZpFmnK5bQUQ26GCU" + "\r" +
"KIsfeXWdupOCias8ksOHT+XqE0WIGGz6aTA78VcQADsCQQDuHn/NsNpNQ0+lbPcA" + "\r" +
"2Nd0elCFvS1iIssRu5qAOOqvzyzbhABNGaCGWa+jzz1yoB2Bm0EMhTB8z8z7n/IX" + "\r" +
"YDhjAkBKw9XWImL3XblmBzTujwTp4UZlt0w4nEKhpIZdSnzSTfbNZrfWco65GVLm" + "\r" +
"MDiPYGXUZKDdY6MUUczUXGKugCQRAkEAvZAgNFK/Z1TXuh0mAlGeLEcXhXCWCZMj" + "\r" +
"UImmNL+a7b0ju9m5F6f4KByL+/+GrpMTClPblCkP8bzINeUeKEfcewJBAM6HcCA7" + "\r" +
"G4Xbg/PeY2338D9IdWdhsRTLBbWBJUGDXXWXumwmZu1Nrqd5sjK111TQilfLTg4G" + "\r" +
"jb5e2Gx7BcieoS4=" + "\r"; /**
* 私钥
*/
private RSAPrivateKey privateKey; /**
* 公钥
*/
private RSAPublicKey publicKey; /**
* 字节数据转字符串专用集合
*/
private static final char[] HEX_CHAR= {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; /**
* 获取私钥
* @return 当前的私钥对象
*/
public RSAPrivateKey getPrivateKey() {
return privateKey;
} /**
* 获取公钥
* @return 当前的公钥对象
*/
public RSAPublicKey getPublicKey() {
return publicKey;
} /**
* 随机生成密钥对
*/
public void genKeyPair(){
KeyPairGenerator keyPairGen= null;
try {
keyPairGen= KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair= keyPairGen.generateKeyPair();
this.privateKey= (RSAPrivateKey) keyPair.getPrivate();
this.publicKey= (RSAPublicKey) keyPair.getPublic();
} /**
* 从文件中输入流中加载公钥
* @param in 公钥输入流
* @throws Exception 加载公钥时产生的异常
*/
public void loadPublicKey(InputStream in) throws Exception{
try {
BufferedReader br= new BufferedReader(new InputStreamReader(in));
String readLine= null;
StringBuilder sb= new StringBuilder();
while((readLine= br.readLine())!=null){
if(readLine.charAt(0)=='-'){
continue;
}else{
sb.append(readLine);
sb.append('\r');
}
}
loadPublicKey(sb.toString());
} catch (IOException e) {
throw new Exception("公钥数据流读取错误");
} catch (NullPointerException e) {
throw new Exception("公钥输入流为空");
}
} /**
* 从字符串中加载公钥
* @param publicKeyStr 公钥数据字符串
* @throws Exception 加载公钥时产生的异常
*/
public void loadPublicKey(String publicKeyStr) throws Exception{
try {
BASE64Decoder base64Decoder= new BASE64Decoder();
byte[] buffer= base64Decoder.decodeBuffer(publicKeyStr);
KeyFactory keyFactory= KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec= new X509EncodedKeySpec(buffer);
this.publicKey= (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("公钥非法");
} catch (IOException e) {
throw new Exception("公钥数据内容读取错误");
} catch (NullPointerException e) {
throw new Exception("公钥数据为空");
}
} /**
* 从文件中加载私钥
* @param keyFileName 私钥文件名
* @return 是否成功
* @throws Exception
*/
public void loadPrivateKey(InputStream in) throws Exception{
try {
BufferedReader br= new BufferedReader(new InputStreamReader(in));
String readLine= null;
StringBuilder sb= new StringBuilder();
while((readLine= br.readLine())!=null){
if(readLine.charAt(0)=='-'){
continue;
}else{
sb.append(readLine);
sb.append('\r');
}
}
loadPrivateKey(sb.toString());
} catch (IOException e) {
throw new Exception("私钥数据读取错误");
} catch (NullPointerException e) {
throw new Exception("私钥输入流为空");
}
} public void loadPrivateKey(String privateKeyStr) throws Exception{
try {
BASE64Decoder base64Decoder= new BASE64Decoder();
byte[] buffer= base64Decoder.decodeBuffer(privateKeyStr);
PKCS8EncodedKeySpec keySpec= new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory= KeyFactory.getInstance("RSA");
this.privateKey= (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法");
} catch (IOException e) {
throw new Exception("私钥数据内容读取错误");
} catch (NullPointerException e) {
throw new Exception("私钥数据为空");
}
} /**
* 加密过程
* @param publicKey 公钥
* @param plainTextData 明文数据
* @return
* @throws Exception 加密过程中的异常信息
*/
public byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception{
if(publicKey== null){
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher= null;
try {
// cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher= Cipher.getInstance("RSA/ECB/NOPADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output= cipher.doFinal(plainTextData);
return output;
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此加密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
}catch (InvalidKeyException e) {
throw new Exception("加密公钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("明文长度非法");
} catch (BadPaddingException e) {
throw new Exception("明文数据已损坏");
}
} /**
* 解密过程
* @param privateKey 私钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception{
if (privateKey== null){
throw new Exception("解密私钥为空, 请设置");
}
Cipher cipher= null;
try {
// cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());
cipher= Cipher.getInstance("RSA/ECB/NOPADDING");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(cipherData);
// byte[] output = cipher.doFinal(enBuff);
return output;
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此解密算法");
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
}catch (InvalidKeyException e) {
throw new Exception("解密私钥非法,请检查");
} catch (IllegalBlockSizeException e) {
throw new Exception("密文长度非法");
} catch (BadPaddingException e) {
throw new Exception("密文数据已损坏");
}
} /**
* 字节数据转十六进制字符串
* @param data 输入数据
* @return 十六进制内容
*/
public static String byteArrayToString(byte[] data){
StringBuilder stringBuilder= new StringBuilder();
for (int i=0; i<data.length; i++){
//取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移
stringBuilder.append(HEX_CHAR[(data[i] & 0xf0)>>> 4]);
//取出字节的低四位 作为索引得到相应的十六进制标识符
stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);
if (i<data.length-1){
stringBuilder.append(' ');
}
}
return stringBuilder.toString();
} public static void main(String[] args){
RSAEncrypt rsaEncrypt= new RSAEncrypt();
//rsaEncrypt.genKeyPair(); String defaultCharsetName = Charset.defaultCharset().displayName();
System.out.println("defaultCharsetName:"+defaultCharsetName); //加载公钥
try {
rsaEncrypt.loadPublicKey(RSAEncrypt.DEFAULT_PUBLIC_KEY);
System.out.println("加载公钥成功");
} catch (Exception e) {
System.err.println(e.getMessage());
System.err.println("加载公钥失败");
} //加载私钥
try {
rsaEncrypt.loadPrivateKey(RSAEncrypt.DEFAULT_PRIVATE_KEY);
System.out.println("加载私钥成功");
} catch (Exception e) {
System.err.println(e.getMessage());
System.err.println("加载私钥失败");
} //测试字符串
String encryptStr= "12345678"; try {
//加密
byte[] en_content = new byte[128];
System.arraycopy(encryptStr.getBytes(),0 , en_content, 0, encryptStr.getBytes().length);
byte[] cipher = rsaEncrypt.encrypt(rsaEncrypt.getPublicKey(), en_content);
//解密
byte[] plainText = rsaEncrypt.decrypt(rsaEncrypt.getPrivateKey(), cipher);
System.out.println("密文长度:"+ cipher.length);
System.out.println(RSAEncrypt.byteArrayToString(cipher));
System.out.println("明文长度:"+ plainText.length);
System.out.println(RSAEncrypt.byteArrayToString(plainText));
System.out.println(new String(plainText));
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}

Java代码中默认公钥和私钥,是之前使用linux命令生成的rsa_public_key.pem和pkcs8_rsa_private_key.pem中的字符串。  

需要注意的地方时,我们要加密的字符串是”12345678“,字符长度为为8为,对该字符串进行RSA加解密时,如果使用NOPADDING方式进行填充,它要求的加密长度为128位的倍数,为此,我们在C/C++和Java代码中生成一个128的缓冲区,然后将要加密的字符串拷贝至该缓冲区,之后将该缓冲区最为参数进行加密。

如果你传入的参数不是128位,而是8为,程序可以正常运行,只是不同平台和语言之间得到的加密字符串不一致,不能在多平台和多语言之间使用openssl。