基於OpenSLL的RSA加密應用(非算法)

分類:IT技術 時間:2016-10-19

基於OpenSLL的RSA加密應用(非算法)

iOS開發中的小夥伴應該是經常用der和p12進行加密解密,而且在通常加密不止一種加密算法,還可以加點兒鹽吧~本文章主要闡述的是在iOS中基於openSLL的RSA加密。一共有兩種方式,一種是基於p12加密解密的,還有一種是博客園官方提供的公鑰字符串加密的,其實兩種都差不多,只不過在iOS中支持crt格式的加密,其實也是一樣的吧~下面就來看看兩種加密的應用。。。


一、RSA加密工具類(der和p12)~

二、OpenSSL 生成密鑰示例~

三、der和p12加密解密Demo

四、公鑰字符串加密工具類~

五、公鑰加密Demo

六、一個關於RSA加密困擾了我幾天的問題~


說在前面的話~本文RSA加密算法並非筆者本人所作~RSA算法網上有一大堆的demo,不過筆者觀察核心的代碼也就只有一兩個版本~所以,筆者也小小的借鑒了一下~


一、RSA加密工具類(der和p12)~

本加密工具適用於DES,AES,RSA加密~下面是代碼,不做講解~因為核心算法的代碼不是本人寫的~筆者只做了整理和封裝~

CryptorTools.h

//
//  CryptorTools.h
//  加密/解密工具
//
//  Created by Erma on 15/4/26.
//  Copyright (c) 2015年 Erma. All rights reserved.
//

#import <Foundation/Foundation.h>

///  加密工具類
///  提供RSA & AES & DES加密方法
@interface CryptorTools : NSObject

#pragma mark - DES 加密/解密
///  DES 加密
///
///  @param data      要加密的二進制數據
///  @param keyString 加密密鑰
///  @param iv        IV向量
///
///  @return 加密後的二進制數據
+ (NSData *)DESEncryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv;

///  DES 加密字符串
///
///  @param string    要加密的字符串
///  @param keyString 加密密鑰
///  @param iv        IV向量
///
///  @return 加密後的 BASE64 編碼字符串
+ (NSString *)DESEncryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;

///  DES 解密
///
///  @param data      要解密的二進制數據
///  @param keyString 解密密鑰
///  @param iv        IV向量
///
///  @return 解密後的二進制數據
+ (NSData *)DESDecryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv;

///  DES 解密
///
///  @param string    要解密的 BASE64 編碼字符串
///  @param keyString 解密密鑰
///  @param iv        IV向量
///
///  @return 解密後的二進制數據
+ (NSString *)DESDecryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;

#pragma mark - AES 加密/解密
///  AES 加密
///
///  @param data      要加密的二進制數據
///  @param keyString 加密密鑰
///  @param iv        IV向量
///
///  @return 加密後的二進制數據
+ (NSData *)AESEncryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv;

///  AES 加密字符串
///
///  @param string    要加密的字符串
///  @param keyString 加密密鑰
///  @param iv        IV向量
///
///  @return 加密後的 BASE64 編碼字符串
+ (NSString *)AESEncryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;

///  AES 解密
///
///  @param data      要解密的二進制數據
///  @param keyString 解密密鑰
///  @param iv        IV向量
///
///  @return 解密後的二進制數據
+ (NSData *)AESDecryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv;

///  AES 解密
///
///  @param string    要解密的 BASE64 編碼字符串
///  @param keyString 解密密鑰
///  @param iv        IV向量
///
///  @return 解密後的二進制數據
+ (NSString *)AESDecryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv;

#pragma mark - RSA 加密/解密算法
///  加載公鑰
///
///  @param filePath DER 公鑰文件路徑
- (void)loadPublicKeyWithFilePath:(NSString *)filePath;

///  加載私鑰
///
///  @param filePath P12 私鑰文件路徑
///  @param password P12 密碼
- (void)loadPrivateKey:(NSString *)filePath password:(NSString *)password;

///  RSA 加密數據
///
///  @param data 要加密的數據
///
///  @return 加密後的二進制數據
- (NSData *)RSAEncryptData:(NSData *)data;

///  RSA 加密字符串
///
///  @param string 要加密的字符串
///
///  @return 加密後的 BASE64 編碼字符串
- (NSString *)RSAEncryptString:(NSString *)string;

///  RSA 解密數據
///
///  @param data 要解密的數據
///
///  @return 解密後的二進制數據
- (NSData *)RSADecryptData:(NSData *)data;

///  RSA 解密字符串
///
///  @param string 要解密的 BASE64 編碼字符串
///
///  @return 解密後的字符串
- (NSString *)RSADecryptString:(NSString *)string;

@end

CryptorTools.m

//
//  CryptorTools.m
//  加密/解密工具
//
//  Created by Erma on 15/4/26.
//  Copyright (c) 2015年 Erma. All rights reserved.
//

#import "CryptorTools.h"
#import <CommonCrypto/CommonCrypto.h>

// 填充模式
#define kTypeOfWrapPadding      kSecPaddingPKCS1

@interface CryptorTools() {
SecKeyRef _publicKeyRef;                             // 公鑰引用
SecKeyRef _privateKeyRef;                            // 私鑰引用
}

@end

@implementation CryptorTools

#pragma mark - DES 加密/解密
#pragma mark 加密
+ (NSData *)DESEncryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv {
return [self CCCryptData:data algorithm:kCCAlgorithmDES operation:kCCEncrypt keyString:keyString iv:iv];
}

+ (NSString *)DESEncryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
NSData *data = http://www.cnblogs.com/Erma-king/p/[string dataUsingEncoding:NSUTF8StringEncoding];
NSData *result = [self DESEncryptData:data keyString:keyString iv:iv];

// BASE 64 編碼
return [result base64EncodedStringWithOptions:0];
}

#pragma mark 解密
+ (NSData *)DESDecryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv {
return [self CCCryptData:data algorithm:kCCAlgorithmDES operation:kCCDecrypt keyString:keyString iv:iv];
}

+ (NSString *)DESDecryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
// BASE 64 解碼
NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
NSData *result = [self DESDecryptData:data keyString:keyString iv:iv];

return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
}

#pragma mark - AES 加密/解密
#pragma mark 加密
+ (NSData *)AESEncryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv   {
return [self CCCryptData:data algorithm:kCCAlgorithmAES operation:kCCEncrypt keyString:keyString iv:iv];
}

+ (NSString *)AESEncryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSData *result = [self AESEncryptData:data keyString:keyString iv:iv];

// BASE 64 編碼
return [result base64EncodedStringWithOptions:0];
}

#pragma mark 解密
+ (NSData *)AESDecryptData:(NSData *)data keyString:(NSString *)keyString iv:(NSData *)iv {
return [self CCCryptData:data algorithm:kCCAlgorithmAES operation:kCCDecrypt keyString:keyString iv:iv];
}

+ (NSString *)AESDecryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
// BASE 64 解碼
NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
NSData *result = [self AESDecryptData:data keyString:keyString iv:iv];

return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
}

#pragma mark 對稱加密&解密核心方法
///  對稱加密&解密核心方法
///
///  @param data      加密/解密的二進制數據
///  @param algorithm 加密算法
///  @param operation 加密/解密操作
///  @param keyString 密鑰字符串
///  @param iv        IV 向量
///
///  @return 加密/解密結果
+ (NSData *)CCCryptData:(NSData *)data algorithm:(CCAlgorithm)algorithm operation:(CCOperation)operation keyString:(NSString *)keyString iv:(NSData *)iv {

int keySize = (algorithm == kCCAlgorithmAES) ? kCCKeySizeAES128 : kCCKeySizeDES;
int blockSize = (algorithm == kCCAlgorithmAES) ? kCCBlockSizeAES128: kCCBlockSizeDES;

// 設置密鑰
NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
uint8_t cKey[keySize];
bzero(cKey, sizeof(cKey));
[keyData getBytes:cKey length:keySize];

// 設置 IV 向量
uint8_t cIv[blockSize];
bzero(cIv, blockSize);
int option = kCCOptionPKCS7Padding | kCCOptionECBMode;
if (iv) {
    [iv getBytes:cIv length:blockSize];
    option = kCCOptionPKCS7Padding;
}

// 設置輸出緩沖區
size_t bufferSize = [data length] + blockSize;
void *buffer = malloc(bufferSize);

// 加密或解密
size_t cryptorSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(operation,
                                      algorithm,
                                      option,
                                      cKey,
                                      keySize,
                                      cIv,
                                      [data bytes],
                                      [data length],
                                      buffer,
                                      bufferSize,
                                      &cryptorSize);

NSData *result = nil;
if (cryptStatus == kCCSuccess) {
    result = [NSData dataWithBytesNoCopy:buffer length:cryptorSize];
} else {
    free(buffer);
    NSLog(@"[錯誤] 加密或解密失敗 | 狀態編碼: %d", cryptStatus);
}

return result;
}

#pragma mark - RSA 加密/解密算法
- (void)loadPublicKeyWithFilePath:(NSString *)filePath; {

NSAssert(filePath.length != 0, @"公鑰路徑為空");

// 刪除當前公鑰
if (_publicKeyRef) CFRelease(_publicKeyRef);

// 從一個 DER 表示的證書創建一個證書對象
NSData *certificateData = [NSData dataWithContentsOfFile:filePath];
SecCertificateRef certificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)certificateData);
NSAssert(certificateRef != NULL, @"公鑰文件錯誤");

// 返回一個默認 X509 策略的公鑰對象,使用之後需要調用 CFRelease 釋放
SecPolicyRef policyRef = SecPolicyCreateBasicX509();
// 包含信任管理信息的結構體
SecTrustRef trustRef;

// 基於證書和策略創建一個信任管理對象
OSStatus status = SecTrustCreateWithCertificates(certificateRef, policyRef, &trustRef);
NSAssert(status == errSecSuccess, @"創建信任管理對象失敗");

// 信任結果
SecTrustResultType trustResult;
// 評估指定證書和策略的信任管理是否有效
status = SecTrustEvaluate(trustRef, &trustResult);
NSAssert(status == errSecSuccess, @"信任評估失敗");

// 評估之後返回公鑰子證書
_publicKeyRef = SecTrustCopyPublicKey(trustRef);
NSAssert(_publicKeyRef != NULL, @"公鑰創建失敗");

if (certificateRef) CFRelease(certificateRef);
if (policyRef) CFRelease(policyRef);
if (trustRef) CFRelease(trustRef);
}

- (void)loadPrivateKey:(NSString *)filePath password:(NSString *)password {

NSAssert(filePath.length != 0, @"私鑰路徑為空");

// 刪除當前私鑰
if (_privateKeyRef) CFRelease(_privateKeyRef);

NSData *PKCS12Data = [NSData dataWithContentsOfFile:filePath];
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
CFStringRef passwordRef = (__bridge CFStringRef)password;

// 從 PKCS #12 證書中提取標示和證書
SecIdentityRef myIdentity;
SecTrustRef myTrust;
const void *keys[] = {kSecImportExportPassphrase};
const void *values[] = {passwordRef};
CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);

// 返回 PKCS #12 格式數據中的標示和證書
OSStatus status = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);

if (status == noErr) {
    CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
    myIdentity = (SecIdentityRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
    myTrust = (SecTrustRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
}

if (optionsDictionary) CFRelease(optionsDictionary);

NSAssert(status == noErr, @"提取身份和信任失敗");

SecTrustResultType trustResult;
// 評估指定證書和策略的信任管理是否有效
status = SecTrustEvaluate(myTrust, &trustResult);
NSAssert(status == errSecSuccess, @"信任評估失敗");

// 提取私鑰
status = SecIdentityCopyPrivateKey(myIdentity, &_privateKeyRef);
NSAssert(status == errSecSuccess, @"私鑰創建失敗");
CFRelease(items);
}

- (NSString *)RSAEncryptString:(NSString *)string {
NSData *cipher = [self RSAEncryptData:[string dataUsingEncoding:NSUTF8StringEncoding]];

return [cipher base64EncodedStringWithOptions:0];
}

- (NSData *)RSAEncryptData:(NSData *)data {
OSStatus sanityCheck = noErr;
size_t cipherBufferSize = 0;
size_t keyBufferSize = 0;

NSAssert(data, @"明文數據為空");
NSAssert(_publicKeyRef, @"公鑰為空");

NSData *cipher = nil;
uint8_t *cipherBuffer = NULL;

// 計算緩沖區大小
cipherBufferSize = SecKeyGetBlockSize(_publicKeyRef);
keyBufferSize = data.length;

if (kTypeOfWrapPadding == kSecPaddingNone) {
    NSAssert(keyBufferSize <= cipherBufferSize, @"加密內容太大");
} else {
    NSAssert(keyBufferSize <= (cipherBufferSize - 11), @"加密內容太大");
}

// 分配緩沖區
cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
memset((void *)cipherBuffer, 0x0, cipherBufferSize);

// 使用公鑰加密
sanityCheck = SecKeyEncrypt(_publicKeyRef,
                            kTypeOfWrapPadding,
                            (const uint8_t *)data.bytes,
                            keyBufferSize,
                            cipherBuffer,
                            &cipherBufferSize
                            );

NSAssert(sanityCheck == noErr, @"加密錯誤,OSStatus == %d", sanityCheck);

// 生成密文數據
cipher = [NSData dataWithBytes:(const void *)cipherBuffer length:(NSUInteger)cipherBufferSize];

if (cipherBuffer) free(cipherBuffer);

return cipher;
}

- (NSString *)RSADecryptString:(NSString *)string {
NSData *keyData = [self RSADecryptData:[[NSData alloc] initWithBase64EncodedString:string options:0]];

return [[NSString alloc] initWithData:keyData encoding:NSUTF8StringEncoding];
}

- (NSData *)RSADecryptData:(NSData *)data {
OSStatus sanityCheck = noErr;
size_t cipherBufferSize = 0;
size_t keyBufferSize = 0;

NSData *key = nil;
uint8_t *keyBuffer = NULL;

SecKeyRef privateKey = _privateKeyRef;
NSAssert(privateKey != NULL, @"私鑰不存在");

// 計算緩沖區大小
cipherBufferSize = SecKeyGetBlockSize(privateKey);
keyBufferSize = data.length;

NSAssert(keyBufferSize <= cipherBufferSize, @"解密內容太大");

// 分配緩沖區
keyBuffer = malloc(keyBufferSize * sizeof(uint8_t));
memset((void *)keyBuffer, 0x0, keyBufferSize);

// 使用私鑰解密
sanityCheck = SecKeyDecrypt(privateKey,
                            kTypeOfWrapPadding,
                            (const uint8_t *)data.bytes,
                            cipherBufferSize,
                            keyBuffer,
                            &keyBufferSize
                            );

NSAssert1(sanityCheck == noErr, @"解密錯誤,OSStatus == %d", sanityCheck);

// 生成明文數據
key = [NSData dataWithBytes:(const void *)keyBuffer length:(NSUInteger)keyBufferSize];

if (keyBuffer) free(keyBuffer);

return key;
}

@end

二、OpenSSL 生成密鑰示例~

生成強度是 1024 的 RSA 私鑰

$ openssl genrsa -out private.pem 1024

執行以代碼生成一個私鑰,Pem文件,其實Pem文件就是一般的文本格式~看下圖~
這是文件:

選擇一個文本編輯器打開次文件可以看到其就是一個普通的文本:
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCfwtWJLpe9QQiBOA/kDVdYGDYko6ieGfaIHiqiHd7Ul13k4gI+
1NgL6SfO/UAhKL6rAwTk9t8/V0bIrbCTBL6hMLc4yJkBFbDK7eLoJNnxaUwl2pLL
BSiTZQQ8vsBC6myUiZDFdCfl2PWvfEMzMYNsCob2Mw4MYWJwNub+MYe7PwIDAQAB
AoGAc8jXy5FKBa5BRK1lzujgWYdKjilSRisY4jPCwDWXzklZkk0+RV0qqw8ye7BN
LvsBnJ0Wif5lc9mEAmLnKtXwdWrHKEi70s69mZZH+ssaP3SGAEug3tY2ojSYixmB
+dWyslVb3dVzxr56fMJLfCBGAhqhmXgy79ruIbnKrDqo6kkCQQDPYCIZRlI0tREa
4y+E2YUqx/x6XPohlJUQoZBJQ3Zt0RQ+afljNxlSOiL4pw9GLwoDhatxzjlMUMnb
b36mP1plAkEAxTib34YEp5nkwpbZ5roAfKRmKgUnezULVCDKS/KiamXURwAUwGGU
aVy9o1akS48C42gsF+NtOe9yq1z9sj6y0wJBAICLZpekL3DcjC3OhbYj35gVPzva
RnJqV7xnabkASHjqEVJe/mexz9BYmTTo2V736Y0lXpC89GeJ7JZJFoiW3MECQDyM
4cZhpiIy7HoVyHa/GpEqBDfYd0OriHveyV1B9D2IYAEgdD6QdvlWQN7aJf0Q vklF
XWxEJe/IpUMZfMZx24MCQDu19hNYYg8863mvGbc7jWAY1Apjx1i/KTXe/6rBjmoS
bxoSEpKNHpW6dgL/6S6WQuB8j3tNUUNj5O99cU6DLsM=
-----END RSA PRIVATE KEY-----

接著跟著筆者一起執行下面的操作吧~

創建證書請求

$ openssl req -new -key private.pem -out rsacert.csr

這時候控制條要求輸入以下一些個人信息~那就跟著提示來吧~

Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:beijing
Locality Name (eg, city) []:beijing
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Erma
Organizational Unit Name (eg, section) []:com
Common Name (e.g. server FQDN or YOUR name) []:Erma
Email Address []:[email protected]

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

這時候生成了一個csr文件

生成證書並且簽名,有效期10年

$ openssl x509 -req -days 3650 -in rsacert.csr -signkey private.pem -out rsacert.crt

轉換格式-將 PEM 格式文件轉換成 DER 格式

$ openssl x509 -outform der -in rsacert.crt -out rsacert.der

導出P12文件

$ openssl pkcs12 -export -out p.p12 -inkey private.pem -in rsacert.crt

最後生成了兩個我們要用的文件,一個p12文件和一個der文件,der文件是公鑰,p12文件是私鑰。我們把這兩個文件拖入我們的Demo中來使用吧~下面是demo~


三、der和p12加密解密Demo

通過這個Demo主要講解上面提到的工具類的使用~

示例化工具類Tool

CryptorTools *tool = [[CryptorTools alloc] init];

1、加載公鑰

NSString *pubPath = [[NSBundle mainBundle] pathForResource:@"rsacert.der" ofType:nil];
[tool loadPublicKeyWithFilePath:pubPath];

2、使用公鑰加密

NSString *result = [tool RSAEncryptString:@"xiaoer"];
NSLog(@"%@",result);

3、加載私鑰 - 密碼是導出P12的密碼

NSString *privatePath = [[NSBundle mainBundle] pathForResource:@"p.p12" ofType:nil];
[tool loadPrivateKey:privatePath password:@"xyz147896321"];

4、使用私鑰解密

NSLog(@"%@", [tool RSADecryptString:result]);

OK~上面是通過der和p12加密的應用過程~下面再來看看字符串公鑰加密的使用方法~


四、公鑰字符串加密工具類~

RSA.h

//
//  RSA.h
//
//  Created by Erma on 15-2-3.
//  Copyright (c) 2015年 Erma. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface RSA : NSObject

+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey;
+ (NSString *)encryptData:(NSData *)data publicKey:(NSString *)pubKey;

@end

RSA.m

#import "RSA.h"
#import <Security/Security.h>

@implementation RSA

/*
static NSString *base64_encode(NSString *str){
NSData* data = http://www.cnblogs.com/Erma-king/p/[str dataUsingEncoding:NSUTF8StringEncoding];
if(!data){
    return nil;
}
return base64_encode_data(data);
}
*/

static NSString *base64_encode_data(NSData *data){
data = [data base64EncodedDataWithOptions:0];
NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return ret;
}

static NSData *base64_decode(NSString *str){
NSData *data = [[NSData alloc] initWithBase64EncodedString:str  options:NSDataBase64DecodingIgnoreUnknownCharacters];
return data;
}

+ (NSData *)stripPublicKeyHeader:(NSData *)d_key{
// Skip ASN.1 public key header
if (d_key == nil) return(nil);

unsigned long len = [d_key length];
if (!len) return(nil);

unsigned char *c_key = (unsigned char *)[d_key bytes];
unsigned int  idx    = 0;

if (c_key[idx++] != 0x30) return(nil);

if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
else idx++;

// PKCS #1 rsaEncryption szOID_RSA_RSA
static unsigned char seqiod[] =
{ 0x30,   0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
    0x01, 0x05, 0x00 };
if (memcmp(&c_key[idx], seqiod, 15)) return(nil);

idx += 15;

if (c_key[idx++] != 0x03) return(nil);

if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
else idx++;

if (c_key[idx++] != '/0') return(nil);

// Now make a new NSData from this buffer
return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
}

//credit: http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/    CryptoUtils.m#l1036
+ (NSData *)stripPrivateKeyHeader:(NSData *)d_key{
// Skip ASN.1 private key header
if (d_key == nil) return(nil);

unsigned long len = [d_key length];
if (!len) return(nil);

unsigned char *c_key = (unsigned char *)[d_key bytes];
unsigned int  idx    = 22; //magic byte at offset 22

if (0x04 != c_key[idx++]) return nil;

//calculate length of the key
unsigned int c_len = c_key[idx++];
int det = c_len & 0x80;
if (!det) {
    c_len = c_len & 0x7f;
} else {
    int byteCount = c_len & 0x7f;
    if (byteCount + idx > len) {
        //rsa length field longer than buffer
        return nil;
    }
    unsigned int accum = 0;
    unsigned char *ptr = &c_key[idx];
    idx += byteCount;
    while (byteCount) {
        accum = (accum << 8) + *ptr;
        ptr++;
        byteCount--;
    }
    c_len = accum;
}

// Now make a new NSData from this buffer
return [d_key subdataWithRange:NSMakeRange(idx, c_len)];
}

+ (SecKeyRef)addPublicKey:(NSString *)key{
NSRange spos = [key rangeOfString:@"-----BEGIN PUBLIC KEY-----"];
NSRange epos = [key rangeOfString:@"-----END PUBLIC KEY-----"];
if(spos.location != NSNotFound && epos.location != NSNotFound){
    NSUInteger s = spos.location + spos.length;
    NSUInteger e = epos.location;
    NSRange range = NSMakeRange(s, e-s);
    key = [key substringWithRange:range];
}
key = [key stringByReplacingOccurrencesOfString:@"/r" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"/n" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"/t" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@" "  withString:@""];

// This will be base64 encoded, decode it.
NSData *data = base64_decode(key);
data = [RSA stripPublicKeyHeader:data];
if(!data){
    return nil;
}

//a tag to read/write keychain storage
NSString *tag = @"RSAUtil_PubKey";
NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

// Delete any old lingering key with the same tag
NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
[publicKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass];
[publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[publicKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
SecItemDelete((__bridge CFDictionaryRef)publicKey);

// Add persistent version of the key to system keychain
[publicKey setObject:data forKey:(__bridge id)kSecValueData];
[publicKey setObject:(__bridge id) kSecAttrKeyClassPublic forKey:(__bridge id)
 kSecAttrKeyClass];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
 kSecReturnPersistentRef];

CFTypeRef persistKey = nil;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)publicKey, &persistKey);
if (persistKey != nil){
    CFRelease(persistKey);
}
if ((status != noErr) && (status != errSecDuplicateItem)) {
    return nil;
}

[publicKey removeObjectForKey:(__bridge id)kSecValueData];
[publicKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
[publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
[publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];

// Now fetch the SecKeyRef version of the key
SecKeyRef keyRef = nil;
status = SecItemCopyMatching((__bridge CFDictionaryRef)publicKey, (CFTypeRef *)&keyRef);
if(status != noErr){
    return nil;
}
return keyRef;
}

+ (SecKeyRef)addPrivateKey:(NSString *)key{
NSRange spos;
NSRange epos;
spos = [key rangeOfString:@"-----BEGIN RSA PRIVATE KEY-----"];
if(spos.length > 0){
    epos = [key rangeOfString:@"-----END RSA PRIVATE KEY-----"];
}else{
    spos = [key rangeOfString:@"-----BEGIN PRIVATE KEY-----"];
    epos = [key rangeOfString:@"-----END PRIVATE KEY-----"];
}
if(spos.location != NSNotFound && epos.location != NSNotFound){
    NSUInteger s = spos.location + spos.length;
    NSUInteger e = epos.location;
    NSRange range = NSMakeRange(s, e-s);
    key = [key substringWithRange:range];
}
key = [key stringByReplacingOccurrencesOfString:@"/r" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"/n" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@"/t" withString:@""];
key = [key stringByReplacingOccurrencesOfString:@" "  withString:@""];

// This will be base64 encoded, decode it.
NSData *data = base64_decode(key);
data = [RSA stripPrivateKeyHeader:data];
if(!data){
    return nil;
}

//a tag to read/write keychain storage
NSString *tag = @"RSAUtil_PrivKey";
NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

// Delete any old lingering key with the same tag
NSMutableDictionary *privateKey = [[NSMutableDictionary alloc] init];
[privateKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass];
[privateKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[privateKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
SecItemDelete((__bridge CFDictionaryRef)privateKey);

// Add persistent version of the key to system keychain
[privateKey setObject:data forKey:(__bridge id)kSecValueData];
[privateKey setObject:(__bridge id) kSecAttrKeyClassPrivate forKey:(__bridge id)
 kSecAttrKeyClass];
[privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
 kSecReturnPersistentRef];

CFTypeRef persistKey = nil;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)privateKey, &persistKey);
if (persistKey != nil){
    CFRelease(persistKey);
}
if ((status != noErr) && (status != errSecDuplicateItem)) {
    return nil;
}

[privateKey removeObjectForKey:(__bridge id)kSecValueData];
[privateKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
[privateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
[privateKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];

// Now fetch the SecKeyRef version of the key
SecKeyRef keyRef = nil;
status = SecItemCopyMatching((__bridge CFDictionaryRef)privateKey, (CFTypeRef *)&keyRef);
if(status != noErr){
    return nil;
}
return keyRef;
}

/* START: Encryption & Decryption with RSA private key */

+ (NSData *)encryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef{
const uint8_t *srcbuf = (const uint8_t *)[data bytes];
size_t srclen = (size_t)data.length;

size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
void *outbuf = malloc(block_size);
size_t src_block_size = block_size - 11;

NSMutableData *ret = [[NSMutableData alloc] init];
for(int idx=0; idx src_block_size){
        data_len = src_block_size;
    }
    
    size_t outlen = block_size;
    OSStatus status = noErr;
    status = SecKeyEncrypt(keyRef,
                           kSecPaddingPKCS1,
                           srcbuf + idx,
                           data_len,
                           outbuf,
                           &outlen
                           );
    if (status != 0) {
        NSLog(@"SecKeyEncrypt fail. Error Code: %d", status);
        ret = nil;
        break;
    }else{
        [ret appendBytes:outbuf length:outlen];
    }
}

free(outbuf);
CFRelease(keyRef);
return ret;
}

+ (NSString *)encryptString:(NSString *)str privateKey:(NSString *)privKey{
NSData *data = [RSA encryptData:[str dataUsingEncoding:NSUTF8StringEncoding] privateKey:privKey];
NSString *ret = base64_encode_data(data);
return ret;
}

+ (NSData *)encryptData:(NSData *)data privateKey:(NSString *)privKey{
if(!data || !privKey){
    return nil;
}
SecKeyRef keyRef = [RSA addPrivateKey:privKey];
if(!keyRef){
    return nil;
}
return [RSA encryptData:data withKeyRef:keyRef];
}

+ (NSData *)decryptData:(NSData *)data withKeyRef:(SecKeyRef) keyRef{
const uint8_t *srcbuf = (const uint8_t *)[data bytes];
size_t srclen = (size_t)data.length;

size_t block_size = SecKeyGetBlockSize(keyRef) * sizeof(uint8_t);
UInt8 *outbuf = malloc(block_size);
size_t src_block_size = block_size;

NSMutableData *ret = [[NSMutableData alloc] init];
for(int idx=0; idx src_block_size){
        data_len = src_block_size;
    }
    
    size_t outlen = block_size;
    OSStatus status = noErr;
    status = SecKeyDecrypt(keyRef,
                           kSecPaddingNone,
                           srcbuf + idx,
                           data_len,
                           outbuf,
                           &outlen
                           );
    if (status != 0) {
        NSLog(@"SecKeyEncrypt fail. Error Code: %d", status);
        ret = nil;
        break;
    }else{
        //the actual decrypted data is in the middle, locate it!
        int idxFirstZero = -1;
        int idxNextZero = (int)outlen;
        for ( int i = 0; i < outlen; i++ ) {
            if ( outbuf[i] == 0 ) {
                if ( idxFirstZero < 0 ) {
                    idxFirstZero = i;
                } else {
                    idxNextZero = i;
                    break;
                }
            }
        }
        
        [ret appendBytes:&outbuf[idxFirstZero+1] length:idxNextZero-idxFirstZero-1];
    }
}

free(outbuf);
CFRelease(keyRef);
return ret;
}


+ (NSString *)decryptString:(NSString *)str privateKey:(NSString *)privKey{
NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSdatabase64DecodingIgnoreUnknownCharacters];
data = [RSA decryptData:data privateKey:privKey];
NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return ret;
}

+ (NSData *)decryptData:(NSData *)data privateKey:(NSString *)privKey{
if(!data || !privKey){
    return nil;
}
SecKeyRef keyRef = [RSA addPrivateKey:privKey];
if(!keyRef){
    return nil;
}
return [RSA decryptData:data withKeyRef:keyRef];
}

/* END: Encryption & Decryption with RSA private key */

/* START: Encryption & Decryption with RSA public key */

+ (NSString *)encryptString:(NSString *)str publicKey:(NSString *)pubKey{
NSData *data = [RSA encryptData:[str dataUsingEncoding:NSUTF8StringEncoding] publicKey:pubKey];
NSString *ret = base64_encode_data(data);
return ret;
}

+ (NSData *)encryptData:(NSData *)data publicKey:(NSString *)pubKey{
if(!data || !pubKey){
    return nil;
}
SecKeyRef keyRef = [RSA addPublicKey:pubKey];
if(!keyRef){
    return nil;
}
return [RSA encryptData:data withKeyRef:keyRef];
}

+ (NSString *)decryptString:(NSString *)str publicKey:(NSString *)pubKey{
NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:NSDataBase64DecodingIgnoreUnknownCharacters];
data = [RSA decryptData:data publicKey:pubKey];
NSString *ret = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return ret;
}

+ (NSData *)decryptData:(NSData *)data publicKey:(NSString *)pubKey{
if(!data || !pubKey){
    return nil;
}
SecKeyRef keyRef = [RSA addPublicKey:pubKey];
if(!keyRef){
    return nil;
}
return [RSA decryptData:data withKeyRef:keyRef];
}

/* END: Encryption & Decryption with RSA public key */

@end

五、公鑰加密Demo

次示例是適用於連個場景,服務器返回一個公鑰字符串到iOS客戶端,還有一種就是博客園官方接口給的公鑰加密~大多數讀者找到這裏的時候都是因為服務器返回一個公鑰字符串如何加密來到這裏的吧~下面看demo代碼~

一、加載公鑰字符竄,本處隱藏,因為保密~

NSString *publicKey = @"YourPublicKey";

二、對賬號密碼加密~

NSString *name = [RSA encryptString:@"你的賬號" publicKey:publicKey];
NSString *password = [RSA encryptString:@"你的密碼" publicKey:publicKey];

三、OK,打印出來看看吧~

NSLog(@"%@",name);
NSLog(@"%@",password);

這個Demo很簡單~不過在做RSA機密的時候遇到了一個問題,看下面~


六、一個關於RSA加密困擾了我幾天的問題~

這個問題困擾了筆者好幾天~之前一直以為是工具類代碼有問題~嘗試了換了各種工具,自己也寫了一個工具類,還是不成功~我在請求博客園官方的服務器一直返回一下錯誤~一個字符串~貼出來看看,錯誤字符串如下~

<!DOCTYPE html>
<html>
<head>
    <title>Base-64 字符數組或字符串的長度無效。</title>
    <meta name="viewport" content="width=device-width" />
    <style>
     body {font-family:"Verdana";font-weight:normal;font-size: .7em;color:black;} 
     p {font-family:"Verdana";font-weight:normal;color:black;margin-top: -5px}
     b {font-family:"Verdana";font-weight:bold;color:black;margin-top: -5px}
     H1 { font-family:"Verdana";font-weight:normal;font-size:18pt;color:red }
     H2 { font-family:"Verdana";font-weight:normal;font-size:14pt;color:maroon }
     pre {font-family:"Consolas","Lucida Console",Monospace;font-size:11pt;margin:0;padding:0.5em;line-height:14pt}
     .marker {font-weight: bold; color: black;text-decoration: none;}
     .version {color: gray;}
     .error {margin-bottom: 10px;}
     .expandable { text-decoration:underline; font-weight:bold; color:navy; cursor:hand; }
     @media screen and (max-width: 639px) {
      pre { width: 440px; overflow: auto; white-space: pre-wrap; word-wrap: break-word; }
     }
     @media screen and (max-width: 479px) {
      pre { width: 280px; }
     }
    </style>
</head>

<body bgcolor="white">

        <span><H1>“/”應用程序中的服務器錯誤。<hr width=100% size=1 color=silver></H1>

        <h2> <i>Base-64 字符數組或字符串的長度無效。</i> </h2></span>

        <font face="Arial, Helvetica, Geneva, SunSans-Regular, sans-serif ">

        <b> 說明: </b>執行當前 Web 請求期間,出現未經處理的異常。請檢查堆棧跟蹤信息,以了解有關該錯誤以及代碼中導致錯誤的出處的詳細信息。

        <br><br>

        <b> 異常詳細信息: </b>System.FormatException: Base-64 字符數組或字符串的長度無效。<br><br>

        <b>源錯誤:</b> <br><br>

        <table width=100% bgcolor="#ffffcc">
           <tr>
              <td>
                  <code>

執行當前 Web 請求期間生成了未經處理的異常。可以使用下面的異常堆棧跟蹤信息確定有關異常原因和發生位置的信息。</code>

              </td>
           </tr>
        </table>

        <br>

        <b>堆棧跟蹤:</b> <br><br>

        <table width=100% bgcolor="#ffffcc">
           <tr>
              <td>
                  <code><pre>

[FormatException: Base-64 字符數組或字符串的長度無效。]
System.Convert.FromBase64_Decode(Char* startInputPtr, Int32 inputLength, Byte* startDestPtr, Int32 destLength) +307
System.Convert.FromBase64CharPtr(Char* inputPtr, Int32 inputLength) +152
System.Convert.FromBase64String(String s) +49
CNBlogs.Infrastructure.Common.RSACryptoService.Decrypt(String cipherText) +40
OpenAPI.Providers.&lt;GrantResourceOwnerCredentials&gt;d__5.MoveNext() +412
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
     Microsoft.Owin.Security.OAuth.&lt;InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantA sync&gt;d__3f.MoveNext() +700

System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task) +13848037
Microsoft.Owin.Security.OAuth.<InvokeTokenEndpointAsync>d__22.MoveNext() +1933
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
Microsoft.Owin.Security.OAuth.<InvokeAsync>d__0.MoveNext() +1211
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
Microsoft.Owin.Security.Infrastructure.<Invoke>d__0.MoveNext() +540
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<RunApp>d__5.MoveNext() +203
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.<DoFinalWork>d__2.MoveNext() +193
Microsoft.Owin.Host.SystemWeb.IntegratedPipeline.StageAsyncResult.End(IAsyncResult ar) +96
System.Web.AsyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +363
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +137

              </td>
           </tr>
        </table>

        <br>

        <hr width=100% size=1 color=silver>

        <b>版本信息:</b>&nbsp;Microsoft .NET Framework 版本:4.0.30319; ASP.NET 版本:4.6.1055.0

        </font>

</body>
</html>

把這個html的編譯為網頁後打開~看到如下效果圖~

筆者在第一次請求的時候就遇到了同樣的問題~一直懷疑是加密工具有誤~所以多次嘗試更換加密工具還是不成功~最後~我發現了錯誤原因~

加密後的字符串中的"+"通過地址欄傳過來時,後臺會解析為空格. 最好的做法是 使用String.Replace("+", "%2B")先將空格編碼,然後再作為參數傳給另一頁面傳遞,這樣頁面在提取參數時才會將“%2B”解碼為加號.但這兒為了簡化,將空格直接還原為"+"。

參考

DES解密時“Base-64字符數組的無效長度”
問題是 在頁面傳送的時候加密了 ,然後解密出來就拋出異常 跟蹤發現是 ++ 在解析REQUEST的時候變成了空格

解決辦法

使用String.Replace("+", "%2B")先將空格編碼,然後再作為參數傳給另一頁面傳遞,這樣頁面在提取參數時才會將“%2B”解碼為加號

下面是一個相關的知識

在使用Convert.ToBase64String()對字符串進行Base64編碼時,註意的幾點:
例:string s = "Hello";
byte[] bytes = Convert.FromBase64String(s);
以上代碼在運行時會拋出FormatException異常.提示為:Base-64字符數組的無效長度

原因:
當Convert.FromBase64String方法的參數s的長度小於4或不是4的偶數倍時,將會拋出FormatException。

例:
Convert.FromBase64String("Hell"); // Normal.
Convert.FromBase64String("Hell "); // Normal.(忽略空格)
Convert.FromBase64String("Hello!"); // throw FormatException.
Convert.FromBase64String("Hello Net"); // Normal.(忽略空格)

最終的解決辦法~

把加密後的字符串中有+號的地方全部換為%2B,代碼如下~

name = [name stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"];
password = [password stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"];

OK~本文記錄的是筆者對RSA應用的總結~


Tags: Copyright interface 都差不多 解密工具 import

文章來源:


ads
ads

相關文章
ads

相關文章

ad