openssl在多平台和多语言之间进行RSA加解密注意事项
首先说一下平台和语言:
系统平台为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。