前言
先膜拜一下 RSA的作者
RSA非对称加密 原理 各种。。。 请自行百度
弯路
最近开发涉及到如何使用RSA进行鉴权 等技术。。。老实说 我找了一圈根本就找到一个真正能在 iOS、Android、web跑通的代码. 浪费了好几天开发时间 就没有一个靠谱能好使的 所以我必须发一篇博客 把真正 好使的代码拿出来 share一下 (当时我真的 想骂娘了 我擦 百度搜出来的 一堆垃圾)
代码实现
第一步 生成公私钥对
命令生成原始 RSA私钥文件 rsa_private_key.pem
1
openssl genrsa -out rsa_private_key.pem 1024
命令将原始 RSA私钥转换为 pkcs8格式
1
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_key.pem
生成RSA公钥 rsa_public_key.pem
1
openssl rsa -in rsa_private_key.pem -pubout
-out rsa_public_key.pem
从上面看出通过私钥能生成对应的公钥,因此我们将私钥
private_key.pem
用在 服务器端 , 公钥 发放给android
跟ios
等前端
第二步 php代码实现
1
* @author sunyazhou (http://www.sunyazhou.com/)
* @version 1.0
* @created 2017-6-25
class Rsa
private static $PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxx
/xxxxxxxxxxxxxxxxxxxxx
y4dDpCOn
A4tBsIdpMMoT+w==
-----END PRIVATE KEY-----';
*返回对应的私钥
private static function getPrivateKey(){
$privKey = self::$PRIVATE_KEY;
return openssl_pkey_get_private($privKey);
* 私钥加密
public static function privEncrypt($data)
if(!is_string($data)){
return null;
return openssl_private_encrypt($data,$encrypted,self::getPrivateKey())? base64_encode($encrypted) : null;
* 私钥解密
public static function privDecrypt($encrypted)
if(!is_string($encrypted)){
return null;
return (openssl_private_decrypt(base64_decode($encrypted), $decrypted, self::getPrivateKey()))? $decrypted : null;
打开
private_key.pem
,将上面的$PRIVATE_KEY,替换成private_key.pem的内容即可,服务器端我们只需要使用私钥来加密解密。
第三步 android端 代码实现
使用java的Cipher类来实现加密解密类,代码如下:
1
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import android.util.Base64;
* @author alun (http://alunblog.duapp.com)
* @version 1.0
* @created 2013-5-17
public class Rsa {
private static final String RSA_PUBLICE =
"xxxxxxxxxxxxxxxxC" + "\r" +
"Qf/xxxxxxxhVuwdNH6aRFE0ms3bkpp/WL4cfVDgnCO" + "\r" +
"+W9J6vRVpuTuD/xxxxxxxxbJeO74fYnYqo/mmyJSeLE5iZg4I" + "\r" +
"Zm5LPWBZWUp3ULCAZQIDAQAB";
private static final String ALGORITHM = "RSA";
* 得到公钥
* @param algorithm
* @param bysKey
* @return
private static PublicKey getPublicKeyFromX509(String algorithm,
String bysKey) throws NoSuchAlgorithmException, Exception {
byte[] decodedKey = Base64.decode(bysKey,Base64.DEFAULT
);
X509EncodedKeySpec x509 = new X509EncodedKeySpec(decodedKey);
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
return keyFactory.generatePublic(x509);
* 使用公钥加密
* @param content
* @param key
* @return
public static String encryptByPublic(String content) {
try {
PublicKey pubkey = getPublicKeyFromX509(ALGORITHM, RSA_PUBLICE);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubkey);
byte plaintext[] = content.getBytes("UTF-8");
byte[] output = cipher.doFinal(plaintext);
String s = new String(Base64.encode(output,Base64.DEFAULT));
return s;
} catch (Exception e) {
return null;
* 使用公钥解密
* @param content 密文
* @param key 商户私钥
* @return 解密后的字符串
public static String decryptByPublic(String content) {
try {
PublicKey pubkey = getPublicKeyFromX509(ALGORITHM, RSA_PUBLICE);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, pubkey);
InputStream ins = new ByteArrayInputStream(Base64.decode(content,Base64.DEFAULT));
ByteArrayOutputStream writer = new ByteArrayOutputStream();
byte[] buf = new byte[128];
int bufl;
while ((bufl = ins.read(buf)) != -1) {
byte[] block = null;
if (buf.length == bufl) {
block = buf;
} else {
block = new byte[bufl];
for (int i = 0; i < bufl; i++) {
block[i] = buf[i];
writer.write(cipher.doFinal(block));
return new String(writer.toByteArray(), "utf-8");
} catch (Exception e) {
return null;
__
注意:
__在初始化
Cipher
对象时,一定要指明使用
"RSA/ECB/PKCS1Padding"
格式如
Cipher.getInstance("RSA/ECB/PKCS1Padding");
打开
rsa_public_key.pem
文件,将上面代码的
RSA_PUBLICE
替换成其中内容即可.
第四步 iOS端代码实现
iOS上没有直接处理RSA加密的API,网上说的大多数也是处理X.509的证书的方法来实现,不过X.509证书是带签名的,在php端
openssl_pkey_get_private
方法获取密钥时,第二个参数需要传签名,而android端实现X.509证书加密解密较为不易,在这里我们利用ios兼容c程序的特点,利用openssl的api实现rsa的加密解密,代码如下:
CRSA.h代码
1
// CRSA.h
// RSA_C_demo
// Created by sunyazhou on 2017/6/25.
#import <Foundation/Foundation.h>
#import <openssl/rsa.h>
#import <openssl/pem.h>
#import <openssl/err.h>
typedef enum {
KeyTypePublic,
KeyTypePrivate
}KeyType;
typedef enum {
RSA_PADDING_TYPE_NONE = RSA_NO_PADDING,
RSA_PADDING_TYPE_PKCS1 = RSA_PKCS1_PADDING,
RSA_PADDING_TYPE_SSLV23 = RSA_SSLV23_PADDING
}RSA_PADDING_TYPE
;
@interface CRSA : NSObject{
RSA *_rsa;
@property(nonatomic, copy)NSString *rsaKeyPath; //证书路径
+ (id)shareInstance;
- (BOOL)importRSAKeyFromeStringWithType:(KeyType)type andKey:(NSString *)keyPath;
- (BOOL)importRSAKeyWithType:(KeyType)type;
- (int)getBlockSizeWithRSA_PADDING_TYPE:(RSA_PADDING_TYPE)padding_type;
- (NSString *)encryptByRsa:(NSString*)content withKeyType:(KeyType)keyType;
- (NSString *)decryptByRsa:(NSString*)content withKeyType:(KeyType)keyType;
CRSA.m
// CRSA.m
// RSA_C_demo
// Created by sunyazhou on 2017/6/25.
#import "CRSA.h"
#define BUFFSIZE 1024
//#import "NSString+Base64.h"
//#import "NSData+Base64.h"
#define PADDING RSA_PADDING_TYPE_PKCS1
@implementation CRSA
+ (id)shareInstance
static KSYCRSA *_crsa = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_crsa = [[self alloc] init];
return _crsa;
- (BOOL)importRSAKeyWithType:(KeyType)type
FILE *file;
NSString *keyName = type == KeyTypePublic ? @"public_key" : @"private_key";
NSString *keyPath = [[NSBundle mainBundle] pathForResource:keyName ofType:@"pem"];
file = fopen([keyPath UTF8String], "rb");
if (NULL != file)
if (type == KeyTypePublic)
_rsa = PEM_read_RSA_PUBKEY(file, NULL, NULL, NULL);
assert(_rsa != NULL);
_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);
assert(_rsa != NULL);
fclose(file);
return (_rsa != NULL) ? YES : NO;
return NO;
- (BOOL)importRSAKeyWithPath:(KeyType)type
FILE *file;
NSString *keyName = type == KeyTypePublic ? @"public_key.pem" : @"private_key.pem";
NSString *keyPath = [self.rsaKeyPath stringByAppendingPathComponent:keyName];
file = fopen([keyPath UTF8String], "rb");
if (NULL != file)
if (type == KeyTypePublic)
_rsa = PEM_read_RSA_PUBKEY(file, NULL, NULL, NULL);
assert(_rsa != NULL);
_rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);
assert(_rsa != NULL);
fclose(file);
return (_rsa != NULL) ? YES : NO;
return NO;
- (BOOL)importRSAKeyFromeStringWithType:(KeyType)type andKey:(NSString *)key{
if (key.length == 0) { return NO; }
BIO *keybio ;
keybio = BIO_new_mem_buf((__bridge void *)(key), -1);
if (keybio==NULL)
printf( "Failed to create key BIO");
return 0;
if(type == KeyTypePublic)
_rsa = PEM_read_bio_RSA_PUBKEY(keybio, &_rsa,NULL, NULL);
_rsa = PEM_read_bio_RSAPrivateKey(keybio, &_rsa,NULL, NULL);
BIO_free(keybio);
return (_rsa != NULL) ? YES : NO;
- (NSString *) encryptByRsa:(NSString*)content withKeyType:(KeyType)keyType
if (![self importRSAKeyWithPath:keyType])
return nil;
// if (![self importRSAKeyWithType:keyType])
// return nil;
int status;
NSUInteger length = [content length];
unsigned char input[length + 1];
bzero(input, length + 1);
int i = 0;
for (; i < length; i++)
input[i] = [content characterAtIndex:i];
NSInteger flen = [self getBlockSizeWithRSA_PADDING_TYPE:PADDING];
char *encData = (char*)malloc(flen);
bzero(encData, flen);
switch (keyType) {
case KeyTypePublic:
status = RSA_public_encrypt(length, (unsigned char*)input, (unsigned char*)encData, _rsa, PADDING);
break;
default:
status = RSA_private_encrypt(length, (unsigned char*)input, (unsigned char*)encData, _rsa, PADDING);
break;
if (status)
NSData *returnData = [NSData dataWithBytes:encData length:status];
free(encData);
encData = NULL;
NSString *ret = [self base64EncodedStringForData:returnData ];
return ret;
free(encData);
encData = NULL;
return nil;
- (NSString *) decryptByRsa:(NSString*)content withKeyType:(KeyType)keyType
if (![self importRSAKeyWithPath:keyType])
return nil;
// if (![self importRSAKeyWithType:keyType])
// return nil;
int status;
NSData *data = [self base64DecodedDataForString:content];
NSUInteger length = [data length];
NSInteger flen = [self getBlockSizeWithRSA_PADDING_TYPE:PADDING];
char *decData = (char*)malloc(flen);
bzero(decData, flen);
switch (keyType) {
case KeyTypePublic:
status = RSA_public_decrypt(length, (unsigned char*)[data bytes], (unsigned char*)decData, _rsa, PADDING);
break;
default:
status = RSA_private_decrypt(length, (unsigned char*)[data bytes], (unsigned char*)decData, _rsa, PADDING);
break;
if (status)
NSMutableString *decryptString = [[NSMutableString alloc] initWithBytes:decData length:strlen(decData) encoding:NSASCIIStringEncoding];
free(decData);
decData = NULL;
return decryptString;
free(decData);
decData = NULL;
return nil;
- (int)getBlockSizeWithRSA_PADDING_TYPE:(RSA_PADDING_TYPE)padding_type
int len = RSA_size(_rsa);
if (padding_type == RSA_PADDING_TYPE_PKCS1 || padding_type == RSA_PADDING_TYPE_SSLV23) {
len -= 11;
return len;
//---------------加密工具方法
- (NSString *)base64EncodedStringForData:(NSData *)data
return [self base64EncodedStringWithWrapWidth:0 data:data];
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth data:(NSData *)data
//ensure wrapWidth is a multiple of 4
wrapWidth = (wrapWidth / 4) * 4;
const char lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
long long inputLength = [data length];
const unsigned char *inputBytes = [data bytes];
long long maxOutputLength = (inputLength / 3 + 1) * 4;
maxOutputLength += wrapWidth? (maxOutputLength / wrapWidth) * 2: 0;
unsigned char *outputBytes = (unsigned char *)malloc(maxOutputLength);
long long i;
long long outputLength = 0;
for (i = 0; i < inputLength - 2; i += 3)
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
outputBytes[outputLength++] = lookup[((inputBytes[i + 1] & 0x0F) << 2) | ((inputBytes[i + 2] & 0xC0) >> 6)];
outputBytes[outputLength++] = lookup[inputBytes[i + 2] & 0x3F];
//add line break
if (wrapWidth && (outputLength + 2) % (wrapWidth + 2) == 0)
outputBytes[outputLength++] = '\r';
outputBytes[outputLength++] = '\n';
//handle left-over data
if (i == inputLength - 2)
// = terminator
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
outputBytes[outputLength++] = lookup[(inputBytes[i + 1] & 0x0F) << 2];
outputBytes[outputLength++] = '=';
else if (i == inputLength - 1)
// == terminator
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0x03) << 4];
outputBytes[outputLength++] = '=';
outputBytes[outputLength++] = '=';
//truncate data to match actual output length
outputBytes = realloc(outputBytes, outputLength);
NSString *result = [[NSString alloc] initWithBytesNoCopy:outputBytes length:outputLength encoding:NSASCIIStringEncoding freeWhenDone:YES];
#if !__has_feature(objc_arc)
[result autorelease];
#endif
return (outputLength >= 4)? result: nil;
- (NSData *)base64DecodedDataForString:(NSString *)string
return [self dataWithBase64EncodedString:string];
- (NSData *)dataWithBase64EncodedString:(NSString *)string
const char lookup[] =
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 99, 99, 99,
99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,
99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99
NSData *inputData = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
long long inputLength = [inputData length];
const unsigned char *inputBytes = [inputData bytes];
long long maxOutputLength = (inputLength / 4 + 1) * 3;
NSMutableData *outputData = [NSMutableData dataWithLength:maxOutputLength];
unsigned char *outputBytes = (unsigned char *)[outputData mutableBytes];
int accumulator = 0;
long long outputLength = 0;
unsigned char accumulated[] = {0, 0, 0, 0};
for (long long i = 0; i < inputLength; i++)
unsigned char decoded = lookup[inputBytes[i] & 0x7F];
if (decoded != 99)
accumulated[accumulator] = decoded;
if (accumulator == 3)
outputBytes[outputLength++] = (accumulated[0] << 2) | (accumulated[1] >> 4);
outputBytes[outputLength++] = (accumulated[1] << 4) | (accumulated[2] >> 2);
outputBytes[outputLength++] = (accumulated[2] << 6) | accumulated[3];
accumulator = (accumulator + 1) % 4;
//handle left-over data
if (accumulator > 0) outputBytes[outputLength] = (accumulated[0] << 2) | (accumulated[1] >> 4);
if (accumulator > 1) outputBytes[++outputLength] = (accumulated[1] << 4) | (accumulated[2] >> 2);
if (accumulator > 2) outputLength++;
//truncate data to match actual output length
outputData.length = outputLength;
return outputLength? outputData: nil;
这里面我增加了 密钥直接从字符串读取的方法 原来方法是 从
NSBundle
读取private_key.pem和 public_key.pem 但是考虑到被篡改 我增加了 密钥直接搞成字符串(把字符串写到本地沙盒然后加载文件的方式) 这样代码 安全就提高了一些 如果能破译.m的话 拿到的也只能是 publicKey(公钥) 只要不能篡改 就是安全的
外部调用
1
NSString *publicKey = @"-----BEGIN PUBLIC KEY-----\n此处替换生成的公钥 记得换行 按照一定规则加'\n' \n-----END PUBLIC KEY-----";
NSString *privateKey = @"-----BEGIN PRIVATE KEY-----\n 此处替换生成的私钥 \n-----END PRIVATE KEY-----";
NSFileManager *fm = [NSFileManager defaultManager];
// 获取Documents目录路径
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
NSString *path = [docDir stringByAppendingFormat:@"/%@",bundleIdentifier];
NSString *publicKeyPath = [path stringByAppendingPathComponent:@"public_key.pem"];
NSString *privateKeyPath = [path stringByAppendingPathComponent:@"private_key.pem"];
BOOL isDir;
BOOL exists = [fm fileExistsAtPath:path isDirectory:&isDir];
if (exists) {
/* file exists */
if (isDir) {
NSError *error = nil;
BOOL pubResult = [publicKey writeToFile:publicKeyPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSLog(@"%@",[error localizedDescription]);
BOOL privateResult = [privateKey writeToFile:privateKeyPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSLog(@"%@",[error localizedDescription]);
}else {
[fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
NSError *error = nil;
BOOL pubResult = [publicKey writeToFile:publicKeyPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSLog(@"%@",[error localizedDescription]);
BOOL privateResult = [privateKey writeToFile:privateKeyPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSLog(@"%@",[error localizedDescription]);
rsa.rsaKeyPath = path;
[rsa importRSAKeyFromeStringWithType:KeyTypePublic andKey:publicKeyPath];
[rsa importRSAKeyFromeStringWithType:KeyTypePrivate andKey:privateKeyPath];
NSString *pubDesc = [rsa encryptByRsa:@"需要加密的字符串" withKeyType:KeyTypePrivate];
NSLog(@"加密内容:%@\n--------\n",encryptString);