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

来源:http://www.cnblogs.com/Erma-king/archive/2016/10/04/5931433.html
-Advertisement-
Play Games

基於OpenSLL的RSA加密應用(非演算法) === iOS開發中的小伙伴應該是經常用der和p12進行加密解密,而且在通常加密不止一種加密演算法,還可以加點兒鹽吧~本文章主要闡述的是在iOS中基於openSLL的RSA加密。一共有兩種方式,一種是基於p12加密解密的,還有一種是博客園官方提供的公鑰字 ...


基於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 = [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 = [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<srclen; idx+=src_block_size){
    //NSLog(@"%d/%d block_size: %d", idx, (int)srclen, (int)block_size);
    size_t data_len = srclen - idx;
    if(data_len > 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<srclen; idx+=src_block_size){
    //NSLog(@"%d/%d block_size: %d", idx, (int)srclen, (int)block_size);
    size_t data_len = srclen - idx;
    if(data_len > 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應用的總結~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 眾所周知,viewPager是能夠滑動的,但有時候我們需要禁止它的滑動(微笑地面對*—……—*)。 情況是這樣的: activity中有一個viewPager,viewPager中加入3個Fragment,第三個Fragment中又使用了一個viewPager,這個viewPager中又加入了幾個F ...
  • 【框架】: 公共部分:左側菜單、TitleBar、RadioGroup(3個RadioButton:X、Y、Z) 選擇X頁面:指示器+ViewPager 【要達成的效果】: (1)左側選擇A,進入X頁面,X1聯網刷新頁面,此時禁止X2預載入—>滑動到X2頁面,X2才聯網刷新—>X3—>X4; (2) ...
  • Activty啟動提供了四種啟動模式。launchMode: standard:每次啟動新的活動視窗(new操作) singleTop:如果在棧頂是目標活動,則直接打開.否則開啟新的活動視窗(new). singleTask和singleInstance基本上相同.差別在於若根活動設置為single ...
  • 當某個activity變得“容易”被系統銷毀時,該activity的onSaveInstanceState就會被執行,除非該activity是被用戶主動銷毀的,例如當用戶按BACK鍵的時候。 註意上面的雙引號,何為“容易”?言下之意就是該activity還沒有被銷毀,而僅僅是一種可能性。這種可能性有 ...
  • 本章節主要為之前項目 JXHomepwner 添加照片功能(項目地址)。具體任務就是顯示一個 UIImagePickerController 對象,使用戶能夠為 JXItem 對象拍照並保存。拍攝的照片會和相應的 JXItem 對象建立關聯,當用戶進入某個 JXItem 對象的詳細視圖的時候,可以看 ...
  • 說明 JSBridge實現示例 目錄 前言 參考來源 楔子 JS實現部分 說明 實現 Android實現部分 說明 JSBridge類 實現 Callback類 實現 Webview容器關鍵代碼 實現 API 類實現 iOS實現部分 說明 WebViewJavascriptBridgeBase 實現 ...
  • 一直想弄清楚onTouchEvent,onInterceptTouchEvent,dispatchTouchEvent的執行順序,以及內部使用switch (event.getAction())中的執行順序。趁這次機會趕緊弄清楚。 重寫上面幾個方法後。我們在LogCat中看看列印的結果。 一.isO ...
  • 說明 JSBridge實現原理 目錄 前言 參考來源 前置技術要求 楔子 原理概述 簡介 url scheme介紹 實現流程 實現思路 第一步:設計出一個Native與JS交互的全局橋對象 第二步:JS如何調用Native 第三步:Native如何得知api被調用 第四步:分析url-參數和回調的格 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...