首先說一下平臺和語言:
系統平臺為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。