基于OpenSLL的RSA加密应用(非算法)
基于OpenSSL的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 []:mr_wangyaojie@163.com
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.<GrantResourceOwnerCredentials>d__5.MoveNext() +412
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) +13847892
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) +61
Microsoft.Owin.Security.OAuth.<InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantA sync>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> 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应用的总结
基于OpenSLL的RSA加密应用(非算法)的更多相关文章
- 基于OpenSSL的RSA加密应用(非算法)
基于OpenSSL的RSA加密应用(非算法) iOS开发中的小伙伴应该是经常用der和p12进行加密解密,而且在通常加密不止一种加密算法,还可以加点儿盐吧~本文章主要阐述的是在iOS中基于openSL ...
- JAVA实现RSA加密解密 非对称算法
首先RSA是一个非对称的加密算法.所以在使用该算法加密解密之前,必须先行生成密钥对.包含公钥和私钥 JDK中提供了生成密钥对的类KeyPairGenerator,实比例如以下: public stat ...
- 秀尔算法:破解RSA加密的“不灭神话”
RSA加密 VS 秀尔算法 作为RSA加密技术的终结者——“太多运算,无法读取”的秀尔算法(Shor’s algorithm)不是通过暴力破解的方式找到最终密码的,而是利用量子计算的并行性,可以快速分 ...
- 秀尔算法:破解RSA加密的“不灭神话” --zz
http://netsecurity.51cto.com/art/201508/488766.htm RSA加密曾被视为最可靠的加密算法,直到秀尔算法出现,打破了RSA的不灭神话. RSA加密 VS ...
- OpenSSL中的大数接口与基于其的自用RSA加密接口设计
本文记录了初次接触OpenSSL中的大数模块,重温了RSA加密流程,使用OpenSSL的接口包装成自用RSA加密接口,并且利用自己的接口演示了Alice与Bob通过RSA加密进行通讯的一个示例. 概览 ...
- 兼容javascript和C#的RSA加密解密算法,对web提交的数据进行加密传输
Web应用中往往涉及到敏感的数据,由于HTTP协议以明文的形式与服务器进行交互,因此可以通过截获请求的数据包进行分析来盗取有用的信息.虽然https可以对传输的数据进行加密,但是必须要申请证书(一般都 ...
- 【加密】RSA加密之算法
RSA公钥加密算法是1977年由Ron Rivest.Adi Shamirh和LenAdleman在(美国麻省理工学院)开发的. RSA算法是一种非对称密码算法,所谓非对称,就是指该算法需要一对密钥, ...
- RSA加密解密算法
/** * RSA加密解密算法 * Class Rsa */ class Rsa { /** * 获取pem格式的公钥 * @param $public_key 公钥文件路径或者字符串 * @retu ...
- 用户登录密码RSA加密后传输的实现,非明文密码传输
在用户登录页面,用户输入密码后,在传送到服务器端时,为防止在密码传送过程中,被如360这种东东给拦截到, 需要在传送前对密码进行加密,然后再传送! 利用RSA加密,在客户端使用公钥对密码进行加密,在服 ...
随机推荐
- 重载与覆盖(java)
一般来说,分为两种情况.一是在同一个类里面有两个或两个以上的同名的方法,这些方法的名字相同,但参数不同,这种情况中叫重载.当你调用方法时,会根据你传进去的参数的类型来决定调用哪个方法.二是一个类继承了 ...
- actor concurrency
The hardware we rely on is changing rapidly as ever-faster chips are replaced by ever-increasing num ...
- shell简单用法笔记(一)
一.linux中主要用的bash shell:查看linux系统中支持的shell种类可用: vim /etc/shell 执行shel脚步的方式: 1.赋予脚步可执行权限,使用相对或绝对路径调用该脚 ...
- 混搭.NET技术
新闻 .NET技术+25台服务器怎样支撑世界第54大网站 再度燃起人们对.NET的技术热情.这篇新闻中透露了StackExchange 在技术方面的混搭,这也是我所崇尚的.因此我也在社区里极力推广Mo ...
- ASP.NET MVC 5 - 控制器
MVC代表: 模型-视图-控制器 .MVC是一个架构良好并且易于测试和易于维护的开发模式.基于MVC模式的应用程序包含: · Models: 表示该应用程序的数据并使用验证逻辑来强制实施业务规则的数据 ...
- 说说Java程序和数据库交互的乱码解决
本文就本人遇到的问题进行讲解 1.通过jdbc直连方式,连接Mysql数据库,从程序向数据库中写入数据出现的乱码解决方案. 当通过程序向Student表中写入一条数据时,写入数据库的内容会产生乱码. ...
- HTTPS那些事(三)攻击实例与防御(转载)
原创地址:http://www.guokr.com/blog/148613/ 在<HTTPS那些事(二)SSL证书>我描述了使用SSL证书时一些需要注意的安全问题,在这一篇文章里面我再 ...
- JQuery学习思维导图版
常用UI资源 参考资料:Jquery教程 dataTables:教程 中文教程 Wizard:教程 Jquery UI demos:教程 selectmenu:教程 jquery-slider:教程 ...
- WCF学习之旅——第一个WCF示例(三)
第五步:创建客户端 WCF应用服务被成功寄宿后,WCF服务应用便开始了服务调用请求的监听工作.此外,服务寄宿将服务描述通过元数据的形式发布出来,相应的客户端就可以获取这些元数据.接下来我们来创建客户端 ...
- android studio sdk 不能更新
网上看到好多sdk不能更新的,解决办法基本上一下,试了下大都没用,,有人说改hosts ,我试了 貌似没用 下面是我亲测可以更新的一种方法:使用镜像地址更新 步骤: 1. 打开 SDK Manager ...