一、http和https的区别
引用度娘的一段话
HTTPS和HTTP的区别
超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息。HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此HTTP协议不适合传输一些敏感信息,比如信用卡号、密码等。
为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS。为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
HTTPS和HTTP的区别主要为以下四点:
一、https协议需要到ca申请证书,一般免费证书很少,需要交费。
二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。(引自度娘)
上面的那一坨总结出来就是,http数据为明文传输,被拦截后就直接明文可以看到数据;而https数据为加密传输,即使被拦截到了,那也是乱码。当然,这里应该有抓包过https的小伙伴表示不服,这个下面会说到的。
因为加上了这一ssl层,所以https在整个传输过程中,大部分时间都是消耗在了ssl的认证、加密中,所以相比于http,速度还是会慢一点的,所以上面说视频流媒体的app可以不用强制https,因为太耗时间了。。。
二、https之服务器简单聊一聊
因为本人并不是服务器开发,所以这个服务器这方面就提一下。
要想从http升级成https,那得先服务器认证。
就是要证明你这个服务器,是你所声明的服务器。没错,就像天朝的要证明你是你自己,你证明你妈是你吗。。。表示心疼需要证明的小伙伴。。。。
那怎么证明你妈是你妈,呸。。。不是,是证明你的服务器,就是你所声明的服务器那?你说你是百度,我还说我是那!!
所以,这里就涉及到了一个权威的机构登场CA (Certificate Authority)!!!
采用https的服务器必须从CA (Certificate Authority)申请一个用于证明服务器用途类型的证书。该证书只有用于对应的服务器的时候,客户端才信任此主机。(一般这种证书都是要花钱的买的。。。。)
举个栗子
你用浏览器访问一些网站的时候,浏览器会自动验证网站的证书,如果证书不是CA签发的,那么浏览器会提示提示你,此网站的证书无效(因为不是指定的机构签发的,有可能是自己签发的),如下图
当不是正规机构签发的证书
有木有人看着眼熟,对,你登录12306的时候,有木有!!
堂堂大铁路局的12306证书竟然不是正规机构签发的!!要知道浏览器连草榴都信任,说明草榴的证书都是正规买的,12306竟然后还是自己签发的。。。就差这点钱么?
如果服务器买好了证书的话,因为要符合苹果的ATS政策
·服务器所有的连接使用TLS1.2以上版本
·HTTPS证书必须使用SHA256以上哈希算法签名
·HTTPS证书必须使用RSA 2048位或ECC 256位以上公钥算法
还有证书还要是符合苹果认同的,最近沃通的证书好像就快到期了,所以是沃通签发的赶紧去重新买。
配置完的可以用这个来查看是否符合苹果的要求,传送门SSL证书 - 腾讯云
三、https流程
其实百度https,上面就有https的流程(我是传送门https_百度百科),所以这里简单的说下,其中的各种算法、参数的交换在下面的步骤就不细说了,请自行传送门。
这里分https的单向认证和双向认证(单向还是双向这个需要服务器去配置的),所以分开来说,要不小伙伴们会蒙的
单向认证
材料:买来的服务器证书server.cer(客户端要放一个,用来验证服务端),客户端,服务器。
数据加密基本原理:RSA加密+对称加密(数据data经对称密钥key加密,然后把对称密钥key经RSA公钥加密)
1、客户端向服务器发起请求。
2、服务器响应到请求,同时把服务器的证书发给客户端。
3、客户端接收到证书,然后和客户端中的证书对比,如果证书不一致或者无效,那么断开连接。如果通过,那么进行第四部。
4、用户产生一个随机密钥,然后经服务器证书中的公钥进行加密,传给服务端。
5、服务端拿到加密数据和加密密钥,用服务器的私钥解开密钥,得到对称密钥key。
6、服务端和客户端互相通讯指定这个密钥为加密密钥。握手结束
7、客户端和服务端开始通讯,通讯数据由对称密钥加密。
简易图(对付看吧)
双向认证
双向认证比单向认证多了一步,就是服务器要认证客户端,按照百度百科上的步骤,客户端应该有一个由CA(或正规机构)签发的p12证书,和CA根证书(签名的p12就是由这个签名的)
//上面的CA根证书和p12证书 其实都可以自签的,下面介绍的也是自签的
材料:买来的服务器证书server.cer(客户端要放一个,用来验证服务端),客户端,服务器端,CA的根证书(放到服务器中,用来验证客户端的证书),p12证书(用来放到客户端,网络请求的时候会传给服务端)。
步骤:就是在上面第4步的时候,要用p12文件来对一段数据进行签名,然后把签名和p12证书,加密的对称密钥(上面的说过的)传给服务器,然后服务器接到以后,会用CA根证书(或自签的根证书)来对证书和签名数据进行验证,如果正确,通讯继续,否则,断开连接。
其他的都是一样的。
四、自签证书的过程
p12和CA根证书,其实也可以自签的,现在说下自签的过程。
首先,先有个工具,xca
xca
就是这个东西,然后安装以后
xca界面
首先,先生生成一个datacode
新建database
填写database名字和存放地址
设置database的密码,并确认
然后就会生成一个这个东西
然后根据这个database生成一个CA根证书(当然,是自签的了)
根证书ca设置
根证书的具体设置
根证书CA私钥的设置
直接点确定,然后就会提示你privatekey创建好了,然后直接确定到下面这个图
根证书创建成功后会在xca中显示出来的
然后导出CA根证书
根证书CA的导出
就是它
这时候,自签的CA根证书就创建好了,这个CA证书是要放到服务器上的。一般默认的CA证书时间是10年。
然后这时候,就要创建自签的p12证书了。选中这个CA证书,然后
创建p12证书
设置p12证书的源头
然后subject里面和设置CA证书一样。
p12证书默认是一年的,如果感觉短的话 可以在Extensions中设置时间。
然后把P12证书放到客户端项目里。
五、iOS端的https双向适配
AFN的https的双向适配2.x和3.0不一样,先说3.0
客户端认证服务端,
shareManager = [AFHTTPSessionManager manager];
shareManager.securityPolicy = [self customSecurityPolicyWithCerName:HPWalletServerCerName];//调用方法
//客户端认证服务端的方法
+ (AFSecurityPolicy *)customSecurityPolicyWithCerName:(NSString *)cerName{
//导入证书路径
NSString *cerPath = [[NSBundle mainBundle] pathForResource:cerName ofType:@""];
//加载证书
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
NSSet *certSet = [NSSet setWithObject:cerData];
//使用AFSSLPinningModeCertificate证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certSet];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = NO;
//是否需要验证域名,默认为YES
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;
return securityPolicy;
}
重写AFN的方法 来让服务器认证客户端
{//新的https
shareManager.securityPolicy = [self customSecurityPolicyWithCerName:HPWalletServerCerName];
//客户端请求验证,重写setSessionDidReceiveAuthenticationChallengeBlock方法
[shareManager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__autoreleasing NSURLCredential *credential = nil;
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if([shareManager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if(credential) {
disposition =NSURLSessionAuthChallengeUseCredential;
} else {
disposition =NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
// client authentication
SecIdentityRef identity = NULL;
SecTrustRef trust = NULL;
NSString *p12 = [[NSBundle mainBundle] pathForResource:@"p12文件的名字" ofType:@"p12"];
NSFileManager *fileManager =[NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:p12]) {
NSLog(@"hpwallet-client.p12:not exist");
} else {
NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
if ([[self class] extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data]) {
SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate(identity, &certificate);
const void *certs[] = {certificate};
CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
disposition =NSURLSessionAuthChallengeUseCredential;
}
}
}
*_credential = credential;
return disposition;
}];
}
+ (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
OSStatus securityError = errSecSuccess;
//client certificate password
NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@“p12的密码“ forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items);
if(securityError == 0) {
CFDictionaryRef myIdentityAndTrust =CFArrayGetValueAtIndex(items,0);
const void*tempIdentity =NULL;
tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity);
*outIdentity = (SecIdentityRef)tempIdentity;
const void*tempTrust =NULL;
tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);
*outTrust = (SecTrustRef)tempTrust;
} else {
NSLog(@"Failedwith error code %d",(int)securityError);
return NO;
}
return YES;
}
AFN2.x适配
客户端认证服务器
manage.securityPolicy = [HttpServiceAFNetworkImp customSecurityPolicy];
+ (AFSecurityPolicy *)customSecurityPolicy
{
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"服务器.cer证书的名字"ofType:@"der"];
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = NO;
//是否需要验证域名,默认为YES
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;
//放入证书
securityPolicy.pinnedCertificates = @[cerData];
return securityPolicy;
}
服务器认证客户端,这个要把AFN的AFURLConnectionOperation.m文件中 - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge方法替换掉 替换成
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];
//倒入证书 NSLog(@"thePath===========%@",thePath);
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
SecIdentityRef identity = NULL;
// extract the ideneity from the certificate
[self extractIdentity :inPKCS12Data toIdentity:&identity];
SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate (identity, &certificate);
const void *certs[] = {certificate};
// CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
// create a credential from the certificate and ideneity, then reply to the challenge with the credential
//NSLog(@"identity=========%@",identity);
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:nil persistence:NSURLCredentialPersistencePermanent];
// credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
并添加下面的方法
- (OSStatus)extractIdentity:(CFDataRef)inP12Data toIdentity:(SecIdentityRef*)identity {
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("证书密码");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12Data, options, &items);
if (securityError == 0)
{
CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
}
if (options) {
CFRelease(options);
}
return securityError;
}