HTTPS总结
HTTPS定义
- 全称:Hypertext Transfer Protocol over Secure Socket Layer
- HTTP的安全版,即HTTP之下TCP之上加入SSL层,与HTTP不同的是不同的默认端口(http通常是80,https通常是443)及一个加密/身份验证层
- HTTPS协议需要到CA申请证书,免费证书很少,需要交费
- HTTP的连接是无状态的;HTTPS协议包含一种类似握手协议的身份认证过程(认证是单向的/双向的)
有两种基本的加解密算法类型
-
对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等;
- 类似传统的账号密码概念,内容是账号密码是密钥
-
非对称加密:密钥成对出现,且根据公钥无法推知私钥,根据私钥也无法推知公钥;加密解密使用不同密钥,公钥加密需要私钥解密,私钥加密需要公钥解密,如RSA。
- 密文 S = A×B,S是加密后的内容,A与B分别是两个大素数(仅可被自己与1整除,如13),这样使用A加密后的结果S只能被B除尽(解开),反之同理。
有两种认证方法(单向/双向)
-
单向认证:一般意义的https,如浏览器访问https网站
- 主要保证服务器是它自己所声明的正确地址
- 客户端和服务端的内容都是通过对称密钥加密的
- 对称密钥是由客户端产生,通过服务器派发出的公钥加密后发送给服务器
-
双向认证:客户端也具有一个自己的证书,如银行的U盾
- 客户端本身具备证书,保密的是传输过程本身而不一定能保证服务器本身是安全的
- 如常见的钓鱼网站或DNS劫持,用户访问的域名并没有指向正确的地址,所以客户端证书本身并无法确保服务器是真实的
- 双向认证是指客户端不仅要向CA验证服务器本身的真实性,服务器在响应数据时也要验证接收方的真实性
- 详细链接 Https detail
证书格式以及转化
证书的标准
PKCS 全称是 Public-Key Cryptography Standards
是由 RSA 实验室与其它安全系统开发商为促进公钥密码的发展而制订的一系列标准
PKCS 目前共发布过 15 个标准。
常见的标准:
- PKCS#7 Cryptographic Message Syntax Standard
- PKCS#10 Certification Request Standard
- PKCS#12 Personal Information Exchange Syntax Standard
- X.509是常见通用的证书格式。所有的证书都符合为Public Key Infrastructure (PKI) 制定的 ITU-T X509 国际标准。
PKCS#7 常用的后缀是: .P7B .P7C .SPC
PKCS#12 常用的后缀有: .P12 .PFX
X.509 DER 编码(ASCII)的后缀是: .DER .CER .CRT
X.509 PAM 编码(Base64)的后缀是: .PEM .CER .CRT
编码与扩展名
X.509证书是一个数字文档,具有扩展名和编码两个属性
- .DER = DER是一种编码,扩展名有三种。以二进制存放,常见于Windows系统
- .PEM = PEM是一种编码,扩展名也是三种,文件开始由一行"—– BEGIN …“开始。
- .CRT = CRT常见于Linux/Unix证书。证书可以是DER编码,也可以是PEM编码。
- .CER = CRT证书的微软型式。也可以用两种编码。
- .KEY = 扩展名KEY用于PCSK#8的公钥和私钥。可以是两种编码。
- 扩展名CER和CRT几乎是同义词,CRT文件和CER文件只有在使用相同编码的时候才可以通过改后缀名代替。
证书之间的转换
只有编码相同可以通过改后缀名代替,正常情况下应该通过专业的编码工具来互相转换,如OpenSSL
"openssl x509 -in 你的证书.crt -out 你的证书.cer -outform der"
CA签名证书与自签名证书
CA签名证书如何验证
HTTPS连接建立过程大致是:
- 客户端和服务端建立一个连接
- 服务端返回一个证书
- 客户端里存有各个受信任的证书机构根证书(IOS8 预装的根证书Apple Support
) - 用这些根证书对服务端返回的证书进行验证,经验证如果证书是可信任的,就生成一个pre-master secret
- 用这个证书的公钥加密后发送给服务端,服务端用私钥解密后得到pre-master secret
- 再根据某种算法生成master secret
- 客户端也同样根据这种算法从pre-master secret生成master secret
- 随后双方的通信都用这个master secret对传输数据进行加密解密。
自签名的证书通过SSL Pinning使用
- 为什么要用SSL Pinning?
如果服务端的证书是从受信任的的CA机构颁发的,验证是没问题的,但CA机构颁发证书比较昂贵,小企业或个人用户 可能会选择自己颁发证书,这样就无法通过系统受信任的CA机构列表验证这个证书的真伪了,所以需要SSL Pinning这样的方式去验证。
- 什么是SSL Pinning?
可以理解为证书绑定,是指客户端直接保存服务端的证书(存入Xcode工程),建立https连接时直接对比服务端返回的和客户端保存的两个证书是否一样,一样就表明证书是真的,不再去系统的信任证书机构里寻找验证。
这适用于非浏览器应用,因为浏览器跟很多未知服务端打交道,无法把每个服务端的证书都保存到本地,但CS架构的手机APP事先已经知道要进行通信的服务端,可以直接在客户端保存这个服务端的证书用于校验。
- 为什么直接对比就能保证证书没问题?
如果中间人从客户端取出证书,再伪装成服务端跟其他客户端通信,它发送给客户端的这个证书不就能通过验证吗?
确实可以通过验证,但后续的流程走不下去,因为下一步客户端会用证书里的公钥加密,中间人没有这个证书的私钥就解不出内容,也就截获不到数据,这个证书的私钥只有真正的服务端有,中间人伪造证书主要伪造的是公钥。
如何使用AFNetworking发送HTTPS请求
建立自己的安全策略(Security Policy)
AFSecurityPolicy分三种验证模式:
- AFSSLPinningModeNone
这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。
- AFSSLPinningModeCertificate
这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。
这里还没弄明白第一步的验证是怎么进行的,代码上跟去系统信任机构列表里验证一样调用了SecTrustEvaluate,只是这里的列表换成了客户端保存的那些证书列表。若要验证这个,是否应该把服务端证书的颁发机构根证书也放到客户端里?
- AFSSLPinningModePublicKey
这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据。
整个AFSecurityPolicy就是实现这这几种验证方式,剩下的就是实现细节了,详见源码。
示例代码
- (void)testClientCertificate {
NSString *url = @"https://218.244.131.231/ManicureShop/api/order/pay/%@";
NSDictionary *dic = @{@"request" : @{
@"orderNo" : @"1409282102222110030643",
@"type" : @(2)
}
};
NSData *postData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
//stringWithFormat 自定义文字混淆函数
NSString *sign = [self signWithSignKey:@"test" params:dic];
NSMutableData *body = [postData mutableCopy];
NSLog(@"%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
url = [NSString stringWithFormat:url, sign];
//设置可以解析的文本
//code =1016错误因为这里
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithArray:@[@"application/json", @"text/plain",@"text/html"]];
//自定义安全策略
manager.securityPolicy = [self customSecurityPolicy];
[manager POST:url parameters:dic success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
}
//某种和服务器约定的混淆方式 默认使用不混淆
-(id) signWithSingleKey:(NSString *)inString params: (NSDictionary *)inDictionnary {
return inString;
}
//AFNetworking的自定义安全措施
-(AFSecurityPolicy *) customSecurityPolicy {
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"工程根目录中证书的名字" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
//初始化时使用证书绑定加密 也可以使用公钥绑定加密 可选有3种模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//允许使用非CA签名的证书
//这里有个问题,如果使用的是IP测试模式 不通过DNS 需要去AFNet的安全.m文件里注销掉验证域名的代码 code=1012因为这里
[securityPolicy setAllowInvalidCertificates:YES];
[securityPolicy setPinnedCertificates:@[certData]];
return securityPolicy;
}
注销域名验证
前面说过,验证站点证书,是通过域名的,如果服务器端站点没有绑定域名(万恶的备案),仅靠IP地址上面的方法是绝对不行的。
需要修改AFNetworking2的源代码!打开AFSecurityPolicy.m文件,找到方法:
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
将下面这部分注释掉
// SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//
// if (!AFServerTrustIsValid(serverTrust)) {
// return NO;
// }
//
// if (!self.validatesCertificateChain) {
// return YES;
// }
这样,AFSecurityPolicy就只会比对服务器证书和内嵌证书是否一致,不会再验证证书是否和站点域名一致了。