RSA加密过程笔记

2017-07-03  本文已影响101人  学习无底

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

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

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命令

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

加密的方式主要分为两种

四、实现过程中踩过的坑

/*!
 @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;
}
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;
}

五、感谢

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

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

加密与签名

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

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

上一篇 下一篇

猜你喜欢

热点阅读