RSA加密过程笔记

一、各种不同后缀名表示的含义

X.509是常见通用的证书格式。所有的证书都符合为Public Key Infrastructure (PKI) 制定的 ITU-T X509 国际标准。

  • .pem : Privacy Enhanced Mail,以 -----BEGIN........开头,以 -----END......结束,内容是BASE64编码
  • .csr : 证书签名请求,这个并不是证书,核心内容是公钥加用户信息
  • .crt : 已经签名的证书
  • .der : Distinguished Encoding Rules ,证书, 二进制格式,不可读
  • .p12 : = CER文件 + 私钥

PS : 系统自带库 公钥文件支持 .der 格式的,私钥支持 .p12 格式

二、openssl 命令生成 rsa 私钥共钥及证书

命令行 切换到相应的文件夹

$ openssl     //开启openssl命令
$ genrsa -out rsa_private_key.pem 1024         //生成私钥
$ rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
 writing RSA key           //生成公钥
$ pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt         //对私钥进行PKCS#8编码,并且不设置密码 
$ req -new -key rsa_private_key.pem -out rsa_cert.csr         //根据私钥创建证书请求,需要填写相关信息
$ x509 -req -days 3650 -in rsa_cert.csr -signkey rsa_private_key.pem -out rsa_cert.crt             //生成证书并且签名,有效期10年
$ x509 -outform der -in rsa_cert.crt -out rsa_cert.der           //转换格式-将 PEM 格式文件转换成 DER 格式
$ pkcs12 -export -out p.p12 -inkey rsa_private_key.pem -in rsa_cert.crt         //导出P12文件,需要设置密码
$ exit       //退出openssl命令

三、加密的方式及开启方法

加密的方式主要分为两种

  • 1、系统的 <Security/Security.h> 中的 SecKeyRef
    共钥可以通过后缀名为 .per 的文件创建,也可以通过共钥字符串创建。
    私钥通过后缀名为 .p12 的文件创建,需要密码,也可以通过私钥字符串创建。
    共钥加密,私钥解密,私钥签名,共钥验证。
  • 2、第三方的 openssl 中的 RSA
    共钥可以通过后缀名为 .pem 的文件创建,也可以通过共钥字符串创建。
    私钥通过后缀名为 .pem的文件创建,也可以通过私钥字符串创建。
    共钥加密,私钥解密,私钥签名,共钥验证。也可以私钥加密,共钥解密。

四、实现过程中踩过的坑

  • 1、对于加密的内容需要转义,并且长度不能超出密钥长度减去11字符,如果过长需要自己截取分段加密。
  • 2、SecKeyRef 加解密中参数的坑
/*!
 @function SecKeyEncrypt
 @abstract Encrypt a block of plaintext.
 @param key Public key with which to encrypt the data.
 @param padding See Padding Types above, typically kSecPaddingPKCS1.
 @param plainText The data to encrypt.
 @param plainTextLen Length of plainText in bytes, this must be less
 or equal to the value returned by SecKeyGetBlockSize().
 @param cipherText Pointer to the output buffer.
 @param cipherTextLen On input, specifies how much space is available at
 cipherText; on return, it is the actual number of cipherText bytes written.
 @result A result code. See "Security Error Codes" (SecBase.h).
 @discussion If the padding argument is kSecPaddingPKCS1 or kSecPaddingOAEP,
 PKCS1 (respectively kSecPaddingOAEP) padding will be performed prior to encryption.
 If this argument is kSecPaddingNone, the incoming data will be encrypted "as is".
 kSecPaddingOAEP is the recommended value. Other value are not recommended
 for security reason (Padding attack or malleability).

 When PKCS1 padding is performed, the maximum length of data that can
 be encrypted is the value returned by SecKeyGetBlockSize() - 11.

 When memory usage is a critical issue, note that the input buffer
 (plainText) can be the same as the output buffer (cipherText).
 */
OSStatus SecKeyEncrypt(
                       SecKeyRef           key,
                       SecPadding          padding,
                       const uint8_t        *plainText,
                       size_t              plainTextLen,
                       uint8_t             *cipherText,
                       size_t              *cipherTextLen)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_2_0);
/*!
 @function SecKeyDecrypt
 @abstract Decrypt a block of ciphertext.
 @param key Private key with which to decrypt the data.
 @param padding See Padding Types above, typically kSecPaddingPKCS1.
 @param cipherText The data to decrypt.
 @param cipherTextLen Length of cipherText in bytes, this must be less
 or equal to the value returned by SecKeyGetBlockSize().
 @param plainText Pointer to the output buffer.
 @param plainTextLen On input, specifies how much space is available at
 plainText; on return, it is the actual number of plainText bytes written.
 @result A result code. See "Security Error Codes" (SecBase.h).
 @discussion If the padding argument is kSecPaddingPKCS1 or kSecPaddingOAEP,
 the corresponding padding will be removed after decryption.
 If this argument is kSecPaddingNone, the decrypted data will be returned "as is".

 When memory usage is a critical issue, note that the input buffer
 (plainText) can be the same as the output buffer (cipherText).
 */
OSStatus SecKeyDecrypt(
                       SecKeyRef           key,                /* Private key */
                       SecPadding          padding,         /* kSecPaddingNone,
                                                             kSecPaddingPKCS1,
                                                             kSecPaddingOAEP */
                       const uint8_t       *cipherText,
                       size_t              cipherTextLen,       /* length of cipherText */
                       uint8_t             *plainText,  
                       size_t              *plainTextLen)       /* IN/OUT */
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_2_0);
//加密实现
- (NSData *)encryptData:(NSData *)data
                      withKeyType:(QKeyType)keyType {
    SecKeyRef keyRef = _pubSecKeyRef;
    if (keyRef == NULL) {
        NSAssert(NO, @"对应的秘钥不在");
        return nil;
    }
    
    int dataLength = (int)data.length;
    int blockSize = (int)SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
 int maxLen =  blockSize;
    if (padding == kSecPaddingPKCS1) {
/**When PKCS1 padding is performed, the maximum length of data that can
 be encrypted is the value returned by SecKeyGetBlockSize() - 11. */
        maxLen -= 11;
    }
    int count = (int)ceil(dataLength * 1.0 / maxLen); // 计算出来的count达不到预期,ceil没有实现向上取整,发现是整数相除
//还有除数一定是一次处理的数据数,照这个错误找了好久
    
    NSMutableData *encryptedData = [[NSMutableData alloc] init] ;
    uint8_t* cipherText = (uint8_t*)malloc(blockSize);
    
    for (int i = 0; i < count; i++) {
        NSUInteger bufferSize = MIN(maxLen, dataLength - i * maxLen);
        NSData *inputData = [data subdataWithRange:NSMakeRange(i * maxLen, bufferSize)];
        bzero(cipherText, blockSize);//初始化
        size_t outlen = blockSize; //刚开始直接用的maxLen ,一直错误,
        
        OSStatus status = SecKeyEncrypt(keyRef,
                                        kSecPaddingPKCS1,
                                        (const uint8_t *)[inputData bytes],
                                        bufferSize,
                                        cipherText,
                                        &outlen);
        if (status == errSecSuccess) {//errSecSuccess == 0
            [encryptedData appendBytes:cipherText length:outlen];
        }else{
            free(cipherText);
            cipherText = NULL;
            return nil;
        }
    }
    free(cipherText);
    cipherText = NULL;
    
    return encryptedData;
}
//解密数据
- (NSData *)decryptEncryptedData:(NSData *)encryptedData
                               withKeyType:(QKeyType)keyType {
    SecKeyRef keyRef = _priSecKeyRef;
    if (keyRef == NULL) {
        NSAssert(NO, @"对应的秘钥不在");
        return nil;
    }
    int dataLength = (int)encryptedData.length;
    int blockSize = (int)SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
    int maxLen = blockSize ; //这个地方不需要减11字符
    int count = (int)ceil(dataLength * 1.0 / blockSize);
    
    NSMutableData *decryptedData = [[NSMutableData alloc] init] ;
    UInt8 *outbuf = malloc(blockSize);
    for (int i = 0; i < count; i++) {
        NSUInteger bufferSize = MIN(maxLen, dataLength - i * maxLen);
        NSData *inputData = [encryptedData subdataWithRange:NSMakeRange(i * maxLen, bufferSize)];
        bzero(outbuf, blockSize);//初始化
        size_t outlen = blockSize;
        
        OSStatus status = SecKeyDecrypt(keyRef,
                                        secPadding(),
                                        (const uint8_t *)[inputData bytes],
                                        bufferSize,
                                        outbuf,
                                        &outlen);
        if (status == errSecSuccess) {
            [decryptedData appendBytes:outbuf length:outlen];
        }else{
            free(outbuf);
            outbuf = NULL;
            return nil;
        }
    }
    
    free(outbuf);
    outbuf = NULL;
    return decryptedData;
}
  • 3、三方 RSA 类加密中遇到的坑
PEM_read_RSAPrivateKey(<#FILE *fp#>, <#RSA **x#>, <#pem_password_cb *cb#>, <#void *u#>)

通过 .pem 文件创建 RSA 时,按照这个函数是可以传入密码,对c不熟,但找了很久才找到 ,别人怎么用的

int pass_cb(char *buf, int size, int rwflag, void* password) {
    snprintf(buf, size, "%s", (char*) password);
    return (int)strlen(buf);
}

但有密码的 .pem 文件仍然创建 RSA 失败,待后续继续努力。

- (NSData *)encryptData:(NSData *)data
                      withKeyType:(QKeyType)keyType {
    RSA *rsa = [self rsaForKey:keyType];
    
    if (rsa == NULL) {
        NSAssert(NO, @"对应的秘钥不在");
        return nil;
    }
    QRSA_PADDING_TYPE type = [self current_PADDING_TYPE];
    NSUInteger length = [self sizeOfRSA_PADDING_TYPE:type andRSA:rsa] * 1.0;
    NSUInteger dataLength = data.length;
    int count = (int)ceil(dataLength * 1.0 / length);
    
    int status;// 处理后的数据长度
    char *encData = (char *)malloc(length);
    NSMutableData *encryptedData = [[NSMutableData alloc] init] ;
    for (int i = 0; i < count; i++) {
        NSUInteger bufferSize = MIN(length, dataLength - i * length);
        NSData *inputData = [data subdataWithRange:NSMakeRange(i * length, bufferSize)];
        bzero(encData, length);//初始化
        
        switch (keyType) {
            case QKeyTypePublic:
                status = RSA_public_encrypt((int)bufferSize,
                                            (unsigned char *)[inputData bytes],
                                            (unsigned char *)encData,
                                            _pubRSA,
                                            type);
                break;
                
            case QKeyTypePrivate:
                status = RSA_private_encrypt((int)bufferSize,
                                             (unsigned char*)[inputData bytes],
                                             (unsigned char*)encData,
                                             _priRSA,
                                             type);
                break;
        }
        
        if (status > 0){//如果失败 status 为 -1 判断成功只能用 >0 
            [encryptedData appendBytes:encData length:status];
           
        }else{
            if (encData) {
                free(encData);
            }
            return nil;
        }
    }
    if (encData){
        free(encData);
    }
    return encryptedData;
}
  • 4、SecKeyRef 签名与验证不支持 MD5

五、感谢

同一组密钥创建的 SecKeyRef 与 RSA 可以互相加解密。

在代码实现过程中,搜了不少代码参考,有些是直接借用,花了几天时间,本文写的代码整体实现后,借鉴的文章链接不能在此一一列举,非常感谢他们。

加密与签名

PS:补充,2018年3月23日
加密:是对数据进行机密性保护;有三种方式:对称加密,公钥加密,私钥加密。三种方法只靠其中任意一种都有不可容忍的缺点,因此将它们结合使用。主要经过以下几个过程:

  • 当信息发送者需要发送信息时,首先生成一个对称密钥,用该对称密钥加密要发送的报文;
  • 信息发送者用信息接收者的公钥加密上述对称密钥;
  • 信息发送者将第一步和第二步的结果结合在一起传给信息接收者,称为数字信封;
  • 信息接收者使用自己的私钥解密被加密的对称密钥,再用此对称密钥解密被发送方加密的密文,得到真正的原文。

签名:主要用于身份验证;保证数据的完整性、一致性以及数据来源的可靠性。主要经过以下几个过程:

  • 信息发送者使用一单向散列函数(HASH函数)对信息生成信息摘要;
  • 信息发送者使用自己的私钥签名信息摘要;
  • 信息发送者把信息本身和已签名的信息摘要一起发送出去;
  • 信息接收者通过使用与信息发送者使用的同一个单向散列函数(HASH函数)对接收的信息本身生成新的信息摘要,再使用信息发送者的公钥对信息摘要进行验证,以确认信息发送者的身份和信息是否被修改过。
    加密是可逆的,签名是不可逆。私钥只能用来签名,公钥用来验证签名。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容