openssl在多平臺和多語言之間進行RSA加解密注意事項

王慶發表於2013-08-22

首先說一下平臺和語言:

系統平臺為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。

相關文章