前言
最近要做iOS SDK
的联网授权,涉及到数据安全验证,因此想到使用RSA
进行签名和验证。 授权主要流程如下:
- 1、客户方前往我方开放平台注册授权,得到
AppId
和AppSecret
。 - 2、客户方集成
SDK
,调用Register
接口传入AppId
和AppSecret
。 - 3、
SDK
将AppId
和客户端平台相关信息提交给服务器后台。 - 4、服务器下发最新服务器时间戳、
sign
、公钥、授权相关数据给客户端。 - 5、客户端使用公钥进行签名验证。
- 6、签名验证通过计算授权时间是否有效。
简单介绍就是服务器端生成秘钥对,使用私钥对客户端和开放平台提交的参数进行签名,然后下发签名和公钥(经过处理的字符串)到客户端,客户端验签通过后进行授权验证。
OpenSSL
使用RSA
需要调用一个很重要的库OpenSSL
,可以使用Cocoapods
在项目中快速集成:
我一般会安装如下版本:
pod 'OpenSSL-Universal', '~> 1.0.2.20'
使用前先导入相关头文件:
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/bio.h>
公钥与私钥
1、PEM公钥和私钥文件生成
为了验证接口的正确性,我们需要先本地生成秘钥对进行本地代码测试:
1.1、在桌面创建文件夹 RSA Key
,终端CD
到此文件夹。
myz@myz mediapipe % cd /Users/myz/Desktop/RSA\ Key
1.2、终端生成1024位私钥
openssl genrsa -out rsa_private_key.pem 1024
myz@myz RSA Key % openssl genrsa -out rsa_private_key.pem 1024
Generating RSA private key, 1024 bit long modulus
........++++++
.................................++++++
e is 65537 (0x10001)
myz@myz RSA Key %
1.3、使用RSA
私钥生成公钥
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
2、通用RSA
秘钥格式
将pem
文件改为txt
后缀打开可以看到秘钥文本
2.1、公钥
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjQRu0fImY/AVasfAneO4G8sID
P3L9XDX+nZq8GBw4vlDzoWIqXGx8ETRMMRx+fdEz3Skdrlsp1+6NcYNSp0Id4b1x
mRw4A5zokwN/C6vcVpLZM86Kc/q+Pi9kWkDRUm32jUmI2qWtqyXIOGZMUIfoSVe/
9czeJ66JFX4zwlZEfwIDAQAB
-----END PUBLIC KEY-----
2.2、私钥
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDjQRu0fImY/AVasfAneO4G8sIDP3L9XDX+nZq8GBw4vlDzoWIq
XGx8ETRMMRx+fdEz3Skdrlsp1+6NcYNSp0Id4b1xmRw4A5zokwN/C6vcVpLZM86K
c/q+Pi9kWkDRUm32jUmI2qWtqyXIOGZMUIfoSVe/9czeJ66JFX4zwlZEfwIDAQAB
AoGAFZiWXWyIVvV8PMY0IEwpspdXQJ/C+bjNmMi5b66K4AmV/9ESVxw5YwDvi14P
ayXhv6AAzEVJfIx8qwxignRKoAZ8pB1pqzFoZfudIsnQHJiukfFuriJeCxAmL6GY
Yjtf0QGGqZQp5S0RUaHeYH3Z7KYztcBQP7Wdn3RYVW9VVUECQQD2/0DBeEdzLUH4
0aV8UlHV+cWAd/ODd3TWYxbJ9WJ1GnsPL70Eg38DEmw95XehReUkFpwWFLRoGhjd
DbldIs5XAkEA64miXwxSGjxgjzcDM3wiZIRAljXjWeXhEugZSZgPLssp4r0Z0bdB
R0t6whtzqwxPGg8opB4aDUuOjpp/A0ISGQJACDoyZv9hqeV9CBO7pmt7jFwYhxH3
y45EFwwP60RANlReewAFFMxog6qublVhab7RRiV2p4mjBMCxyVM2tHJ/WwJAbYEm
qTPsM+BgMBUuetA6mSrXcD6Lfa8fbg/UOd/lJyczSQQLrfGZ+tB/uSDULPDjEcV8
apjIGehH1crERDqCeQJBAIDxsUBMiPxwDnYRf9d1QDFgRfe8XU54bpjzWCUNo0+e
rVVst5NNybClxvh7gWX4PgWxR2g1uIkCecvROTRMIzo=
-----END RSA PRIVATE KEY-----
3、公钥与私钥的读取
RSA
加密的public key
格式有多种,常见的有两种:一种密钥头为-----BEGIN RSA PUBLIC KEY-----
,一种开头为-----BEGIN PUBLIC KEY-----
,二者分别对应RSA
的PKCS#1
和PKCS#8
格式。使用OpenSSL
库加载RSA
的公钥时,使用的函数也不同。以字符串公钥为例,对PKCS#1
格式的密钥加载使用PEM_read_bio_RSAPublicKey()
函数,对PKCS#8
格式公钥的加载使用PEM_read_bio_RSA_PUBKEY()
函数。private key
读取通常使用 PEM_read_bio_RSAPrivateKey
函数。
3.1 加载秘钥数据到BIO
将秘钥数据转换为 BIO
对象有一下两种方式:
- 通过
BIO_new_mem_buf
函数读取
// 从字符串读取
NSString *keyString = @"";
const char *buffer = [keyString UTF8String];
bio = BIO_new_mem_buf(buffer, (int)strlen(buffer));
// 从字节读取
NSData *keyData = data;
bio = BIO_new_mem_buf((const void*)[keyData bytes], (int)keyData.length);
- 通过
BIO_puts
生成
// 从字节读取
NSData *keyData = data;
BIO *bio = BIO_new(BIO_s_mem());
BIO_puts(bio, (void*)[keyData bytes]);
3.2 从BIO
对象读取公钥或者私钥
RSA *SLRSAReadKeyFromBIO(BIO *bio, NSString *key, BOOL isPublicKey) {
if (bio == NULL) return NULL;
if (!isPublicKey) {
// 读取私钥
RSA *rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
BIO_free_all(bio);
return rsa;
}
NSString *pkcs1_header = @"-----BEGIN RSA PUBLIC KEY-----";
// NSString *pkcs8_header = @"-----BEGIN PUBLIC KEY-----";
RSA *rsa = NULL;
if (key && [key containsString:pkcs1_header]) {
rsa = PEM_read_bio_RSAPublicKey(bio, &rsa, NULL, NULL);
}
else {
rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
}
BIO_free_all(bio);
return rsa;
}
3.3 秘钥读取接口
RSA *SLRSAReadKey(id key, BOOL isPublicKey) {
if (!key) {
return NULL;
}
BIO *bio = NULL;
NSString *key = nil;
if ([keyObject isKindOfClass:[NSData class]]) {
NSData *keyData = (NSData*)keyObject;
bio = BIO_new_mem_buf((const void*)[keyData bytes], (int)keyData.length);
}
else if ([keyObject isKindOfClass:[NSString class]]) {
key = (NSString*)keyObject;
const char *buffer = [key UTF8String];
bio = BIO_new_mem_buf(buffer, (int)strlen(buffer));
}
if (bio == NULL) {
NSLog(@"--bio new mem buf failed--");
return NULL;
}
return SLRSAReadKeyFromBIO(bio, key, isPublicKey);
}
4、服务器公钥的组装
通常服务器返回的公钥格式为去掉header和end格式的base64字符串:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjQRu0fImY/AVasfAneO4G8sID P3L9XDX+nZq8GBw4vlDzoWIqXGx8ETRMMRx+fdEz3Skdrlsp1+6NcYNSp0Id4b1x mRw4A5zokwN/C6vcVpLZM86Kc/q+Pi9kWkDRUm32jUmI2qWtqyXIOGZMUIfoSVe/ 9czeJ66JFX4zwlZEfwIDAQAB
需要对这些字符串处理后才可以正常读入内存,如果后端有做特殊处理,客户端请做差异化处理:
NSString *SLRSAPEMKeyFromBase64(NSString *base64Key, BOOL isPublicKey) {
NSMutableString *result = [NSMutableString string];
if (isPublicKey) {
[result appendString:@"-----BEGIN PUBLIC KEY-----\n"];
}else{
[result appendString:@"-----BEGIN RSA PRIVATE KEY-----\n"];
}
[result appendString:@""""];
int count = 0;
for (int i = 0; i < [base64Key length]; ++i) {
unichar c = [base64Key characterAtIndex:i];
if (c == '\n' || c == '\r') {
continue;
}
[result appendFormat:@"%c", c];
if (++count == 64) {
[result appendString:@"\n"];
[result appendString:@""""];
count = 0;
}
}
if (isPublicKey) {
[result appendString:@"\n-----END PUBLIC KEY-----"];
}else{
[result appendString:@"\n-----END RSA PRIVATE KEY-----"];
}
return result;
}
加密与解密
在正确读取秘钥到内存后,我们就可以进行加解密操作了,RSA公钥和私钥都可以用来加密和解密,一般成对使用,如果使用公钥加密,则可用私钥解密,如果使用私钥加密,则可用公钥解密,通常正确的方式应该是使用公钥加密、私钥解密。
RSA加密
输入utf-8字符串加密后得到字节数据需要转为base64用于传输
//
int SLRSAEncrypt(BOOL isPublic, RSA *rsa, NSString *src, NSString **dest, int padding) {
if (rsa == NULL || src.length <= 0 || dest == NULL) {
if (rsa) RSA_free(rsa);
return -1;
}
int flen = RSA_size(rsa);
char *dst = (char*)malloc(flen + 1);
bzero(dst, flen);
NSData *srcData = [src dataUsingEncoding:NSUTF8StringEncoding];
int ret = -1;
if (isPublic) {
ret = RSA_public_encrypt((int)srcData.length, (uint8_t*)[srcData bytes], (uint8_t*)dst, rsa, padding);
}
else {
ret = RSA_private_encrypt((int)srcData.length, (uint8_t*)[srcData bytes], (uint8_t*)dst, rsa, padding);
}
if (ret < 0) {
isPublic ? NSLog(@"--rsa public encrypt failed--") : NSLog(@"--ras private encrypt failed--");
RSA_free(rsa);
free(dst);
return ret;
}
NSData *encryptData = [NSData dataWithBytes:(const void*)dst length:sizeof(char)*flen];
*dest = [encryptData base64EncodedStringWithOptions:0];
RSA_free(rsa);
free(dst);
return ret;
}
RSA解密
需要解密的密文一般是Base64格式,需要先base64解码在进行rsa解密,解密字节可以转换为对应的格式字符串用于显示
int SLRSADecrypt(BOOL isPublic, RSA *rsa, NSString *src, NSString **dest, int padding) {
if (rsa == NULL || src.length <= 0 || dest == NULL) {
if (rsa) RSA_free(rsa);
return -1;
}
int flen = RSA_size(rsa);
char *dst = (char*)malloc(flen + 1);
bzero(dst, flen);
NSData *srcData = [[NSData alloc]initWithBase64EncodedString:src options:0];
int ret = -1;
if (isPublic) {
ret = RSA_public_decrypt((int)srcData.length, (uint8_t*)[srcData bytes], (uint8_t*)dst, rsa, padding);
}
else {
ret = RSA_private_decrypt((int)srcData.length, (uint8_t*)[srcData bytes], (uint8_t*)dst, rsa, padding);
}
if (ret < 0) {
isPublic ? NSLog(@"--rsa public encrypt failed--") : NSLog(@"--ras private encrypt failed--");
RSA_free(rsa);
free(dst);
return ret;
}
NSData *decryptData = [NSData dataWithBytes:(const void*)dst length:sizeof(char)*flen];
*dest = [[NSString alloc]initWithData:decryptData encoding:NSUTF8StringEncoding];
RSA_free(rsa);
free(dst);
return ret;
}
签名与验证
RSA 加密方案和 RSA 签名方案是不同的,通常是私钥负责签名,公钥负责验证。
私钥签名
私钥签名通常放在服务器端,签名后服务器下发sign
和公钥给客户端,客户端进行验签
// sha1签名,输出签名后的base64字符串
int SLRSASha1SignWithKey(id key, NSString *src, NSString **sign) {
if ( !key || src.length <= 0 || sign == NULL) {
return -1;
}
RSA *rsa = SLRSAReadKey(key, NO);
if (rsa == NULL) {
NSLog(@"--read rsa private key from bio failed--");
return -1;
}
NSData *srcData = [src dataUsingEncoding:NSUTF8StringEncoding];
unsigned char digest[SHA_DIGEST_LENGTH];
SHA1((const unsigned char *) [srcData bytes], (size_t)srcData.length, digest);
unsigned char *signValue = (unsigned char *)malloc(256);
unsigned int sign_len;
int ret = RSA_sign(NID_sha1, digest, SHA_DIGEST_LENGTH, signValue, &sign_len, rsa);
if (ret == 1) {
NSData* data = [NSData dataWithBytes:signValue length:sign_len];
*sign = [data base64EncodedStringWithOptions:0];
}
free(signValue);
RSA_free(rsa);
return ret;
}
公钥验证
客户端验证签名通过后可以进行其他操作
// 结果返回1则验证正确
int SLRSASha1VerifyWithPublicKey(id key, NSString *src, NSString *sign) {
if ( !key || src.length <= 0 || sign.length <= 0) {
return -1;
}
RSA *rsa = SLRSAReadKey(key, YES);
if (rsa == NULL) {
NSLog(@"--read rsa public key from bio failed--");
return -1;
}
NSData *srcData = [src dataUsingEncoding:NSUTF8StringEncoding];
unsigned char digest[SHA_DIGEST_LENGTH];
SHA1((const unsigned char *) [srcData bytes], (size_t)srcData.length, digest);
// SHA_CTX sha_ctx = { 0 };
// unsigned char digest[SHA_DIGEST_LENGTH];
// int rc = 1;
// rc = SHA1_Init(&sha_ctx);
// if (1 != rc) { return NO; }
//
// rc = SHA1_Update(&sha_ctx, [srcData bytes], srcData.length);
// if (1 != rc) { return NO; }
//
// rc = SHA1_Final(digest, &sha_ctx);
// if (1 != rc) { return NO; }
// 将base64签名转换为字节
NSData *signData = [[NSData alloc]initWithBase64EncodedString:sign options:0];
int ret = RSA_verify(NID_sha1, digest, SHA_DIGEST_LENGTH, (const uint8_t*)[signData bytes], (uint32_t)signData.length, rsa);
RSA_free(rsa);
return ret;
}
小结
RSA
非对称加密算法在日常通讯安全中有着广泛的应用,程序员应该掌握并灵活运用来解决日常问题。本章只是对RSA加解密、签名与验证的基本使用做了一下演示,后续会进一步对RSA
算法和OpenSSL
开源库进行研究并更新文章。
今天的文章【RSA】RSA加密、解密、签名与验证分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/22010.html