首先了解一下几个相关概念,以方便后面遇到的问题的解决:
- RSA算法:1977年由Ron Rivest、Adi Shamirh和LenAdleman发明的,RSA就是取自他们三个人的名字。算法基于一个数论:将两个大素数相乘非常容易,但要对这个乘积的结果进行 因式分解却非常困难,因此可以把乘积公开作为公钥。该算法能够抵抗目前已知的所有密码攻击。RSA算法是一种非对称算法,算法需要一对密钥,使用其中一个 加密,需要使用另外一个才能解密。我们在进行RSA加密通讯时,就把公钥放在客户端,私钥留在服务器。
- DER, PEM:既然使用RSA需要一对密钥,那么我们当然是要先使用工具来生成这样一对密钥了。在linux、unix下,最简单方便的就是使用openssl 命令行了。而DER、PEM就是生成的密钥可选择的两种文件格式。DER是Distinguished Encoding Rules的简称,是一种信息传输语法规则,在ITU X.690中定义的。在ios端,我们的公钥就是需要这样一种格式的,我们可以从Certificate, Key, and Trust Services Reference这 篇文档的SecCertificateCreateWith
Data函数的data参数的说明中看到。而PEM格式是一种对DER进行封装的格式,他只是 把der的内容进行了base64编码并加上了头尾说明。openssl命令行默认输出的都是PEM格式的文件,要能够在ios下使用,我们需要指定使用 DER或者先生成PEM然后转换称DER。
使用openssl命令行生成密钥对
1 | openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem |
按照提示,填入私钥的密码,签名证书的组织名、邮件等信息之后,就会生成包含有公钥的证书文件public_key.der合私钥文件 private_key.pem。public_key.der文件用于分发到ios客户端进行公钥加解密,而private_key.pem文件留在服 务器端供php使用。当然,如果为了在服务器端进行加解密测试,那么我们还可以生成一个服务器端PHP使用的pem公钥文件:
1 | openssl rsa - in private_key.pem -pubout -out public_key.pem |
上面这个命令就会根据输入的私钥文件生成pem格式的公钥文件了。从这里也可以看到,根据私钥,我们是可以生成相对应的公钥的,这也就是为什么我们要把公钥放在客户端,而不是私钥放在客户端的原因了。
服务器端PHP的加解密函数
闲话不多说,贴一段代码,肯定能看懂的了
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
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
52
53
54
55
56
|
class
RSAEncryptTest {
const
PRIVATE_KEY = "-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICxjBABgkqhkiG9w0BBQ0w
MBQGCCqGSIb3DQMHBAihDOZw
kpWcZ5YyUEWUw87DFKrG7dkp
7T7KJ0nbUXo4we658Ez0EHAn
1rEZ0j2o9U40ouDqUVxTpq7Z
6X4YPBQLCJIcaLZORoAQ01cx
MQ6SMYDAgiOLqSCiMc0VABwf
+QRCJ2fa7nOd7UXzCDfKh5Hhq
LP0QhfGI3o9VT7r5qihGxtXt
OPjtuBBBHv/oafQ3jqYnrI4R12ZrEU0acm8
I3iCzUlJPQF0t07bKexayvfW
TcY/sReO/J4beGk3
//c7PImKIOcIvKF9Gp99l/+BM/LMZ7Thd/qwMOV6Eb3T4BvY
ItC+P5Lr29XeINmLRHXKwr27uTxX
+Cwi03ISTEcZbNxLRGArtFUOI
7aP4/4I9Malnt0VjRKYPBCkTvVhoW
-----
END
ENCRYPTED PRIVATE KEY-----";
const
PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUA
7CVjClhRTk3nn+2NNaP4Bi5N/A18rdrV6clNAGUz4i/5q/VQXeLiGYYqgmAkKCJe
gReMsfcnoOSWu+Tvxih/48pu1hwBrmMLFZPOOUWQ9YjQ
HV7sk9x6BKz9QeLi5QIDAQAB
-----
END
PUBLIC KEY-----";
private
static
$private_key
;
private
static
$public_key
;
public
static
function
private_encrypt(
$str
){
self::setup_key();
if
(openssl_private_encrypt(
$str
,
$encrypted
, self::
$private_key
))
return
$encrypted
;
}
public
static
function
private_decrypt(
$str
){
self::setup_key();
if
(openssl_private_decrypt(
$str
,
$decrypted
, self::
$private_key
))
return
$decrypted
;
}
public
static
function
public_decrypt(
$str
){
self::setup_key();
if
(openssl_public_decrypt(
$str
,
$decrypted
, self::
$public_key
))
return
$decrypted
;
}
public
static
function
public_encrypt(
$str
){
self::setup_key();
if
(openssl_public_encrypt(
$str
,
$encrypted
, self::
$public_key
))
return
$encrypted
;
}
private
static
function
setup_key(){
if
(!self::
$private_key
){
// 这里的test就是在生成证书的时候设置的私钥密码
self::
$private_key
= openssl_pkey_get_private(self::PRIVATE_KEY,
"test"
);
}
if
(!self::
$public_key
)
self::
$public_key
= openssl_pkey_get_public(self::PUBLIC_KEY);
}
}
|
IOS客户端的加解密
首先我们需要导入Security.framework,在ios中,我们主要关注四个函数
- SecKeyEncrypt:使用公钥对数据进行加密
- SecKeyDecrypt:使用私钥对数据进行解密
- SecKeyRawVerify:使用公钥对数字签名和数据进行验证,以确认该数据的来源合法性。什么是数字签名,可以参考百度百科这篇文章?
- SecKeyRawSign:使用私钥对数据进行摘要并生成数字签名
从这几个函数中,我们可以看到,我们使用公钥能做的事情就有两个:加密数据,以及对服务器端发来的数据进行签名认证,但是如果你想跟我之前想的一 样,要使用公钥来对数据进行解密,那就没有自带API了。如果想在服务器端使用私钥加密数据,然后再在客户端使用公钥进行解密,以图这样来对交互数据进行 加密,看来是行不通的。其实也应该是这样,公钥是公开的,因为他可以编译到二进制文本里面就认为他不能被获取其实是不对的。同时,RSA因为都是做大数的 运算,算法性能上比较差,如果做大数据量的加解密,对IOS来讲,肯定也是不合适的。
这里就把使用公钥进行加密的代码贴出来:
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
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
52
53
54
55
56
57
58
59
60
|
// 我们在前面使用openssl生成的public_key.der文件的base64值,用你自己的替换掉这里
#define RSA_KEY_BASE64 @"MIIC5DCCAk2gAwIBAgIJALUk
DTjERMA8GA1UECAwIU2hhbmd
xEDAOBgNVBAMMB1lvcmsuR3U
XDTExMTEyNTAyNDUzM1owgYo
MBgNVBAoMBUJhaXlpMQ4wDAY
yMEBnbWFpbC5jb20wgZ8wDQY
DJZqo30LVPXXL9nHcYXBTulJ
ezP1B2eWKz4kLIuSUKOmt0h3
EGDAWgBSIiLi2mehEgi/MwRZOld1mLlhl7TAMBgNVHRM
DzNr8pB0idfI+Farl46OZnW5ZwPu3dvSmhQ+yRdh7Ba54JCyvRy0JcWB+fZgO4QorNRbVVbBSuPg6wLzP
Iena6Z60aFWRwhazd6+hIsKTMTExaWjndblEbhAsjdp
static
SecKeyRef
_public_key=
nil
;
+ (
SecKeyRef
) getPublicKey{
// 从公钥证书文件中获取到公钥的SecKeyRef指针
if
(_public_key ==
nil
){
NSData
*certificateData = [Base64 decode:RSA_KEY_BASE64];
SecCertificateRef
myCertificate =
SecPolicyRef
myPolicy = SecPolicyCreateBasicX509
SecTrustRef
myTrust;
OSStatus
status = SecTrustCreateWithCertif
SecTrustResultType trustResult;
if
(status == noErr) {
status = SecTrustEvaluate(myTrust, &trustResult);
}
_public_key = SecTrustCopyPublicKey(myTrust);
CFRelease
(myCertificate);
CFRelease
(myPolicy);
CFRelease
(myTrust);
}
return
_public_key;
}
+ (
NSData
*) rsaEncryptString:(
NSString
*) string{
SecKeyRef
key = [
self
getPublicKey];
size_t
cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t
*cipherBuffer = malloc(cipherBufferSize *
sizeof
(
uint8_t
));
NSData
*stringBytes = [string dataUsingEncoding:
NSUTF8StringEncoding
];
size_t
blockSize = cipherBufferSize - 11;
size_t
blockCount = (
size_t
)ceil([stringBytes length] / (
double
)blockSize);
NSMutableData
*encryptedData = [[[
NSMutableData
alloc] init] autorelease];
for
(
int
i=0; i<blockCount; i++) {
int
bufferSize = MIN(blockSize,[stringBytes length] - i * blockSize);
NSData
*buffer = [stringBytes subdataWithRange:
NSMakeRange
(i * blockSize, bufferSize)];
OSStatus
status = SecKeyEncrypt(key, kSecPaddingPKCS1, (
const
uint8_t
*)[buffer bytes],
[buffer length], cipherBuffer, &cipherBufferSize);
if
(status == noErr){
NSData
*encryptedBytes = [[
NSData
alloc] initWithBytes:(
const
void
*)cipherBuffer length:cipherBufferSize];
[encryptedData appendData:encryptedBytes];
[encryptedBytes release];
}
else
{
if
(cipherBuffer) free(cipherBuffer);
return
nil
;
}
}
if
(cipherBuffer) free(cipherBuffer);
//
//
return
encryptedData;
}
|