前言
本文是对 Js Lim 的一篇博客的翻译,原文链接如下:
http://jslim.net/blog/2013/06/24/rsa-decryption-on-ios/
若本文有翻译不妥的地方,欢迎大家批评指正,我也期望能够与各位大神探讨交流。
译文
在 iOS 上对 RSA 加密相关的问题折腾了几周,现在我希望跟大家一起分享我调研的结果。
首先,利用 openssl 产生密钥对。(在终端中输入一下命令即可)
openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem -days 3650
对于上面的命令,有几点需要说明:
public_key.der 是 X509 证书,在 iOS 中后缀名应该为 .der 而不是 .pem
private_key.pem 即为可以用来解密的密钥
rsa:1024 是密钥的长度,密钥的长度越长,越安全(基本上 1024 bits 就可以认为是安全的,2048 bits 则是极其安全的)
-days 证书的有效期,在这里,我们设置的有效期为十年
然后,拖拽 public_key.der 文件加入到你的 iOS 项目中,并且创建 RSA 类如下
RSA.h
#import <Foundation/Foundation.h>
@interface RSA : NSObject {
SecKeyRef publicKey;
SecCertificateRef certificate;
SecPolicyRef policy;
SecTrustRef trust;
size_t maxPlainLen;
}
- (NSData *) encryptWithData:(NSData *)content;
- (NSData *) encryptWithString:(NSString *)content;
- (NSString *) encryptToString:(NSString *)content;
@end
‘
RSA.m
#import "RSA.h"
@implementation RSA
- (id)init {
self = [super init];
NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:@"public_key" ofType:@"der"];
if (publicKeyPath == nil) {
NSLog(@"Can not find pub.der");
return nil;
}
NSDate *publicKeyFileContent = [NSData dataWithContentsOfFile:publicKeyPath];
if (publicKeyFileContent == nil) {
NSLog(@"Can not read from pub.der");
return nil;
}
certificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef)publicKeyFileContent);
if (certificate == nil) {
NSLog(@"Can not read certificate from pub.der");
return nil;
}
policy = SecPolicyCreateBasicX509();
OSStatus returnCode = SecTrustCreateWithCertificates(certificate, policy, &trust);
if (returnCode != 0) {
NSLog(@"SecTrustCreateWithCertificates fail. Error Code: %ld", returnCode);
return nil;
}
SecTrustResultType trustResultType;
returnCode = SecTrustEvaluate(trust, &trustResultType);
if (returnCode != 0) {
return nil;
}
publicKey = SecTrustCopyPublicKey(trust);
if (publicKey == nil) {
NSLog(@"SecTrustCopyPublicKey fail");
return nil;
}
maxPlainLen = SecKeyGetBlockSize(publicKey) - 12;
return self;
}
- (NSData *) encryptWithData:(NSData *)content {
size_t plainLen = [content length];
if (plainLen > maxPlainLen) {
NSLog(@"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
return nil;
}
void *plain = malloc(plainLen);
[content getBytes:plain
length:plainLen];
size_t cipherLen = 128; // currently RSA key length is set to 128 bytes
void *cipher = malloc(cipherLen);
OSStatus returnCode = SecKeyEncrypt(publicKey, kSecPaddingPKCS1, plain,
plainLen, cipher, &cipherLen);
NSData *result = nil;
if (returnCode != 0) {
NSLog(@"SecKeyEncrypt fail. Error Code: %ld", returnCode);
}
else {
result = [NSData dataWithBytes:cipher
length:cipherLen];
}
free(plain);
free(cipher);
return result;
}
- (NSData *) encryptWithString:(NSString *)content {
return [self encryptWithData:[content dataUsingEncoding:NSUTF8StringEncoding]];
}
- (NSString *) encryptToString:(NSString *)content {
NSData *data = [self encryptWithString:content];
return [self base64forData:data];
}
// convert NSData to NSString
- (NSString*)base64forData:(NSData*)theData {
const uint8_t* input = (const uint8_t*)[theData bytes];
NSInteger length = [theData length];
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t* output = (uint8_t*)data.mutableBytes;
NSInteger i;
for (i=0; i < length; i += 3) {
NSInteger value = 0;
NSInteger j;
for (j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger theIndex = (i / 3) * 4;
output[theIndex + 0] = table[(value >> 18) & 0x3F];
output[theIndex + 1] = table[(value >> 12) & 0x3F];
output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
}
- (void)dealloc{
CFRelease(certificate);
CFRelease(trust);
CFRelease(policy);
CFRelease(publicKey);
}
@end
对该类的使用方法:
#import "RSA.h"
RSA *rsa = [[RSA alloc] init];
if (rsa != nil) {
// just post the string to server
NSLog(@"%@", [rsa encryptToString:@"This is plaintext"]);
} else {
NSLog(@"Error");
}
对于 iOS 客户端的解密到此简单介绍完毕,现在看看 PHP 服务端的解密,在这之前,我们需要下载 phpseclib 库来进行解密。(请自行下载)
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . 'phpseclib');
include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
$rsa->setPassword('yourPassword');
$rsa->loadKey(file_get_contents('/path/to/private_key.pem'));
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
echo $rsa->decrypt(base64_decode($_POST['ciphertext']));