- 小程序请求数据双向混合加密和防篡改+防重放攻击的实现
前言 大家好,借着中秋放假明日又要上班的这个晚上,平常又没空,趁这个时间点就决定来一篇。 [图片] 我们都知道微信小程序的服务端API上,官方可谓是做足了心思,对用户的数据进行加密,虽然对我们开发者来说似乎是一种麻烦,但是从长远角度来看,是十分有必要的,用户隐私高于一切。 那么在小程序的开发过程中与后端对接接口时是否有想过这样的问题呢? HTTPS真的百分百安全吗? 数据被成功抓包后安全吗? 数据被篡改后还有效吗? 请求被重放后安全吗? 世界上没有绝对安全的系统,但我们可以让它被破解的成本变高。 本篇文章专业性并不高,如果存在错误请大家为我指出来,以免误导别人,谢谢! 以下我们将小程序端称为C端,服务端称为S端,服务端代码是Node.js,仅供参考,但原理都一样,后端可以是其它语言。 思考 围绕着以上的问题,探究一下问题的答案? 本部分只对问题做思考,具体实现请参考下面的实现部分。 HTTPS真的百分百安全吗?只能说是相对安全,当然,在微信小程序的沙箱环境里,HTTPS通信会更加安全,否则官方可能会要求我们对请求加密了对吧。但百密一疏,C端是否存在漏洞,假设C端安全了难道S端就安全嘛?细思恐极,退一万步讲,百分百安全是不存在的,由于篇幅问题相关漏洞大家可以搜索探究。 假设数据被中间人成功抓包,如果数据是明文传输,那么将导致数据泄露,因此对数据进行加密是必要的,但应该如何加密呢?如何做到密钥安全?C端和S端如何进行数据的加解密?RSA和AES加密应该使用哪种加密明文?如何充分发挥RSA和AES两个加密算法的特长? 假设数据被篡改,如果因为请求数据被篡改而导致严重后果,那么很大程度上其实是代码设计有问题,正常设计中安全应被摆在重要的位置,至少不应该出现购买某样商品时是通过C端发送商品价格给S端调起支付那样(只需篡改商品价格即可支付极少的钱购买商品),如果代码设计并不存在严重问题,那么数据被篡改也是不可忽视的,我们需要进行数据签名,让不同的数据拥有唯一的MD5哈希值,如果数据被篡改,通过哈希值即可判断数据是否被篡改,当然也可能会有人问,既然数据被篡改,那攻击者会不会重新生成一个哈希值代替?是的。但是我们有接下来会说到的key,key会作为签名的数据变量之一,由于攻击者并不知道key值因此无法重新签名,key值建议不是固定值而是周期性更换的随机值,例如随着用户的登录态而产生并随之抹除。 假设请求被重放,大部分时候由于数据被加密和防篡改处理过,攻击者并无法直接获得数据或篡改,但如果通过劫持到的登录认证请求的原始数据并重新发起该请求,则攻击者将可能获得重要的认证数据,使得系统将攻击者作为正常用户处理,后续的请求攻击者仍能伪装成正常的用户进行后续攻击。 期望效果 在开始实现之前先看一下完成后的效果,以下是截取了Network中其中一个GET请求发送的数据: [图片] 实际上发送的数据是如下图所示: [图片] 然后是该请求返回的数据: [图片] 实际上返回的数据是如下图所示: [图片] 整个流程: [图片] 实现 由于整个流程在C端的实现上顺序反过来的,因此下面的步骤也将是反向而行。 工具库下载 工欲善其事,必先利其器!这三个库是经过修改压缩的,支持在小程序上使用并且体积可观(总共69.1KB,如果不涉及密码哈希处理只需要前两个库,体积只有65.3KB),接下来的实现操作将会使用到,建议大家可以根据实际情况对功能进行二次封装: CryptoJS.js:点击下载 RSA.js:点击下载 SHA256.js(可选):点击下载 在线的各类加解密工具(可选,可以收藏起来,平时测试挺有用):点击访问 请求数据防重放 要防范请求重放攻击,首先需要了解Unix时间戳 timestamp概念,和时间戳不一样的是它的单位是秒,事实上这个需求也只需要秒级即可。除此之外还将用到另一个值:nonce,它是一个随机产生并只能被使用一次的值,长度自定,请求越频繁长度需要越长(降低同一时间产生相同nonce的几率),C端发送请求时需要将timestamp和生成的nonce加入发送的参数中。那么,如何将两者结合呢?我这为防重放的目标下了一个简单的定义。 同样的请求只能发生一次,且请求必须在规定时间内发出。 何为同样的请求?不是指两次发送的参数一致就是一样的,而是连timestamp和nonce也一样才算是同样的请求。 那么S端如何确认其是同样的请求呢? S端每次接收到一个请求,都会将该请求的nonce存入缓存并保持60秒(这个阈值不一定是60秒,可以根据实际需要定义),时间过后该值将被移除,建议S端采用Redis存储nonce,这样可省去检测和移除nonce的代码。如果S端发现当前请求的nonce存在于已存储的nonce之中,则此请求发生重复,那么timestamp有何用? 如果只使用nonce我们只能保证该请求60秒内不会重复,但60秒后依然任人宰割,这不是要的结果。所以timestamp将用来限制时间,S端时间戳减去C端发送请求的时间戳,得到的差值为N秒,如果N秒大于60秒则此请求过期,那么则可以保证,60秒内因为nonce相同而被判为请求重放,60秒后因为时间差超过而被判为请求已过期,因此确保了请求不会被重放。。 以下展示三种情况: [代码]C端时间戳:1568487720 //2019/9/15 03:02:00 C端NONCE:5rKbMs2Fm3 C端发送请求 -> S端接收请求 S端时间戳:1568487722 //2019/9/15 03:02:02 CS端时间差:1568487722 - 1568487720 = 2 C端NONCE是否存在于缓存:false 【重放校验通过】 [代码] [代码]C端时间戳:1568487722 //2019/9/15 03:05:22 C端NONCE:IzFEs52bAC C端发送请求 -> S端接收请求 S端时间戳:1568487922 //2019/9/15 03:02:00 CS端时间差:1568487922 - 1568487722 = 200 C端NONCE是否存在于缓存:false 【重放校验不通过,请求已过期,因为时间差超过60秒】 [代码] [代码]C端时间戳:1568487720 //2019/9/15 03:02:00 C端NONCE:IxwPHQU0nA C端发送请求 -> S端接收请求 S端时间戳:1568487722 //2019/9/15 03:02:02 CS端时间差:1568487722 - 1568487720 = 2 C端NONCE是否存在于缓存:true 【重放校验不通过,此请求为重放请求,因为nonce已经存在,此请求已经完成,不可重复】 [代码] timestamp和nonce将作为参数参与下面部分的签名。 请求数据防篡改 C端数据签名 首先通过对参数按照参数名进行字典排序(调过一些第三方API的朋友应该明白),假设当前需要传输的参数如下: [代码]{ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3" } [代码] 进行字典排序,参数名顺序应为: [代码]const keys = Object.keys(data); //获得参数名数组 keys.sort(); //字典排序 console.log(key); //["a", "b", "c", "nonce", "timestamp"]; [代码] 参数字典排序后应和参数一起拼接为字符串,至于使用什么拼接符就要与S端商量了,如果参数值是一个数组或一个对象(如c为[1,2,3])那么可以将数据值转为JSON字符串再拼接。以上参数拼接后字符串如下: [代码]a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720 [代码] 下一步是计算拼接字符串的MD5哈希值了嘛? 不是。因为这样拼接的字符串很容易被攻击者伪造签名并篡改数据,这样就失去了签名的意义了。也就是说缺了一个key值。key值又从何而来?上面思考部分有提到建议从登录后发放,并且这个key与该用户的登录态绑定,登录态有效期间,将使用这个key进行请求的签名与验签,至于如何鉴别用户,我们会在最终发送给S端的参数加入一个sessionId作为登录态唯一标识,这里不加是因为这部分数据是需要参与后续的加密的,而sessionId不参与加密。 但是可能又会有一个问题,登录前没有key怎么实现的登录请求?事实上,登录请求并不怕篡改,因为攻击者自己也不知道账号密码,所以无需提供key用于登录请求的签名。 提到登录密码这个需要注意一点,密码不能明文传输,请计算哈希值后传输,S端比对账户密码哈希值即可确认是否正确,同样S端非特殊情况也不能明文存储密码,建议SHA-1或更高级的SHA-256计算后的值,MD5值可能被使用彩虹表(一种为各种常见密码建立的MD5映射表)破解。SHA256计算的库已在上面工具库下载提供。 拼接上我们登录时随机生成的key: [代码]a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720&key=gUelv79KTcFaCkVB [代码] 接下来计算32位MD5哈希值 为什么上面说MD5会被破解而这里却用MD5计算?因为此处计算MD5的目的并不是为了隐藏明文数据,而只是用于数据校验 此处引入了CryptoJS.js [代码]const CryptoJS = require('./CryptoJS'); const signStr = "a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720&key=gUelv79KTcFaCkVB"; const sign = CryptoJS.MD5(signStr).toString(); //a42af0962de99e698d27030c5c9d3b0e [代码] 这么一来我们的数据签名阶段就完成了,然后需要把签名加入参数之中,将和参数一起传输,需要注意的是传输参数无需在意参数名排序。以下是当前的参数处理结果: [代码]{ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" } [代码] 其实从上面这两部分内容来看,会发现防重放和防篡改是相辅相成的,就像两兄弟一样,少了谁都干不好这件事。 S端验证签名 既然有签名那必定也有验签,验签流程其实就是重复C端的前面流程并比对CS两端得出的签名值是否一致。S端取得请求数据后(假设数据未加密,暂时不讨论解密),将除了sign之外的参数名进行字典排序,sign用一个临时变量存下,然后排好序的参数和C端一样拼接得到字符串。 接下来根据上面部分提到的未加密参数sessionId获得用户登录态并获取到该状态的临时key拼接到字符串末尾,接下来进行MD5计算即可得到S端方获得的签名,此时与请求中携带的sign比较是否一致则可确定签名是否有效,如果不一致返回签名错误。具体流程请参考以下: [代码]C端发送请求 -> S端接收请求 //请求参数为data const _sign = data.sign; //排序并拼接除sign外的参数 let signStr = a=789&b=456&c=123&nonce=5rKbMs2Fm3×tamp=1568487720 const sessionId = ...; //用户的sessionId const sessionData = getUserSessionData(sessionId);//根据sessionId查询用户登录态sessionData并取出key,并拼接到字符串尾部 const key = sessionData.key signStr += key; //MD5计算并比对,代码仅供参考 const sign = crypto.md5(signStr); if(sign !== _sign) { //签名错误! } [代码] 请求数据加解密 有了上面部分的参数处理铺垫后,接下来就该开始本文章最核心的加解密,在这之前我们先了解AES和RSA两种加密算法。了解概念后我们再来思考一下两者的一些特性。 AES加解密需要密钥,除某些模式外还需要提供初始化向量,可使用密钥解出明文,是对称加密算法。 RSA加解密需要一对密钥,分别为公钥和私钥,公钥加密,私钥解密,是非对称加密算法。 两者在加密长文本性能上AES占优势。 根据这些让我们发现,他们可以形成互补关系,RSA加解密安全性高但长文本处理性能不及AES。AES加解密长文本性能优于RSA但需要明文密钥和向量加解密,密钥的安全性成问题。 那么在C端生成随机的AES密钥和向量,使用密钥和向量使用aes-128-cbc加密模式(也可以根据实际需要采用其它的模式)加密真正需要传输的参数(参数则是经过防重放+防篡改处理的参数)得到encryptedData,然后将该密钥使用RSA公钥加密得到encryptedKey,下面我顺便把AES加密的向量也一起加密了得到encryptedIV,这样就完美的互补了对方的缺点,既能够较快的完成数据加密又能保证密钥安全性,两全其美。 整个个加解密流程如下图所示: [图片] 下面的四个小章节将逐一描述流程实现: C端加密数据 C端加密后的数据应如下(并非固定格式,根据自己需要定制): [代码]{ "sessionId": "xxxxxxxx", "encryptedData": "xxxxxx", "encryptedKey": "xxxxxx", "encryptedIV": "xxxxxxx" } [代码] 但RSA公钥又是怎么发放到C端的呢?答案是在登录认证的时候服务器下发的,登录成功时服务器会创建RSA密钥对并把公钥发放给C端,私钥存在服务器上该用户的登录态数据中。流程如下所示: [代码]C端发起登录请求 -> S端接收登录请求 S端登录认证是否通过 true S端生成RSA密钥对 - publicKey , privateKey S端查询相关用户信息 S端生成登录态信息 -> 向登录态信息存入privateKey私钥,登录态信息类似如下 "xxxxxx": { id: "xxxxx", authData: { key: "xxxxxx", privateKey: "xxxxxx" } } S端返回登录态唯一标识sessionId和publicKey公钥以及相关用户信息 -> C端 C端存储登录态信息于本地,后续请求将使用服务器提供的公钥进行加密 [代码] 其中privateKey就是该用户当前登录态所使用的解密私钥,C端通过公钥加密后的AES密钥数据只能用该私钥解密。 如果希望登录阶段的请求也加密,那么可以手动生成一个RSA密钥对,然后客户端放置一个固定的公钥,服务器也使用一个固定的私钥进行登录阶段的加解密。 具体实现如下: 此处引入了CryptoJS.js和RSA.js [代码]const CryptoJS = require('./CryptoJS'); const RSA = require('./RSA.js'); //假设当前已登录成功并获得S端下发的RSA公钥且已存入本地存储 //createRandomStr为生成随机大小写英文和数字的字符串 const aesKey = createRandomStr(16); //生成AES128位密钥 16字节=128位 const aesIV = createRandomStr(16); //生成初始化向量IV const raw = JSON.stringfly({ "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" }); const encryptedData = CryptoJS.AES.encrypt(data: raw, key: aesKey, { iv: aesIV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); //使用CBC模式和Pkcs7填充加密 const authData = wx.getStorageSync("authData"); //读取本地存的RSA公钥 RSA.setPublicKey(authData.publicKey); //设置RSA公钥 const encryptedKey = RSA.encrypt(aesKey); //RSA加密AES加密密钥 const encryptedIV = RSA.encrypt(aesIV); //RSA加密AES加密初始化向量,是否加密向量可由自己决定 //最后的处理结果 const result = { sessionId: authData.sessionId, encryptedData, encryptedKey, encryptedIV }; [代码] S端解密数据 [代码]const crypto = require('crypto'); const cryptojs = require('crypto-js'); const { sessionId, encryptedData, encryptedKey, encryptedIV } = requestData; const authData = getUserSessionData(sessionId); //获取用户登录态数据 const privateKey = authData.privateKey; const aesKey = crypto.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, encryptedKey); //使用私钥解密得到AES密钥 const aesIV = crypto.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, encryptedIV); //使用私钥解密得到AES iv向量 const key = cryptojs.enc.Base64.parse(aesKey); const iv = cryptojs.enc.Utf8.parse(aesIV); const decryptedData = cryptojs.AES.decrypt(encryptedData, key, { iv, mode: cryptojs.mode.CBC, padding: cryptojs.pad.Pkcs7 }); //采用与加密统一的模式和填充进行解密 const { "c": 123, "b": 456, "a": 789, "timestamp": 1568487720, "nonce": "5rKbMs2Fm3", "sign": "a42af0962de99e698d27030c5c9d3b0e" } = decryptedData; //至此解密得到C端传来的数据 [代码] S端加密数据 当S端处理完C端的请求后应加密响应数据,那么加密响应数据应该使用什么密钥呢?既然C端已经将加密的密钥发送过来了,那么干脆将C端使用的AES密钥拿来加密响应数据就可以了。加密的数据传回C端后,C端只需使用该请求所使用的AES加密密钥进行解密即可。响应的加密数据如下: [代码]const cryptojs = require('crypto-js'); const responseData = JSON.stringify(...); //此为S端需要返回给C端的数据 const aesKey = ...; //此为之前C端用来加密数据的AES密钥 const aesIV = createRandomStr(16); //生成初始化向量IV const encryptedData = cryptojs.AES.encrypt(data, aesKey, { iv: aesIV, mode: cryptojs.mode.CBC, padding: cryptojs.pad.Pkcs7 }); //加密响应数据 const encryptedResponse = { "encryptedData": encryptedData, "iv": aesIV }; //得到加密后的响应数据并返回给C端 [代码] C端解密数据 C端接收到S端的响应数据后应对加密的数据进行解密,此次解密就是单纯的AES解密了,使用发起请求时用于加密数据的AES密钥配合响应数据的iv向量对encryptedData进行解密,得到解密后的数据即为S端真正的响应数据。实现过程如下: 此处引入了CryptoJS.js [代码]const CryptoJS = require('./CryptoJS'); const key = ...; //之前用于加密请求参数的AES加密密钥 const { encryptedData, iv } = responseData; const aesIV = CryptoJS.enc.Utf8.parse(iv); const aesKey = CryptoJS.enc.Utf8.parse(key); const decryptedData = CryptoJS.AES.decrypt(encryptedData, aesKey, { iv: aesIV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); //AES解密响应数据 const { ... } = decryptedData; //得到解密后的响应数据 [代码] 结语 第一次写这么长的文章,可能存在大量纰漏,如果大佬发现问题欢迎指出(`・ω・´)我会马上修改。 也许小程序的运行环境会比我想象中的更安全 也许HTTPS也会比我想象中的更安全 也许Web服务器引擎也比我想象中的更安全 但,安全不总是先行一步的吗?
2019-10-24 - 【高校开发者】双生日记开发经验分享
双生日记开发经验分享 Hello,我是双生日记的 Founder & Developer Airing。该项目的小程序端获得了 2018 C4——微信小程序应用开发赛的一等奖,而 iOS 端则获得了 2018 C4——移动应用创新赛的一等奖,目前累计注册用户已达 1 万+,并仍在不断开发维护中~ 本文将简要概括我们团队在产品的整个研发流程中的所做的工作,更侧重于介绍产品研发与团队管理的方法。 我将整个产品的研发分为以下四步: 立项 设计 开发 维护 [图片] 可以看到,以上四步形成了项目流程的闭环,使得产品能够良性发展。接下来我来具体谈谈这四步工作的内容。 1. 立项 项目立项是所有环节最开始的部分,我觉得也是最重要的部分,它的工作内容类似于“产品经理”的职责。虽然我是 Founder,但产品的探讨还是与大家共同完成的。具体而言,这个环节有两个内容: 产品脑暴 文档整理 1.1 产品脑暴 首先,我会先在团队中提出我的想法,并创建一个讨论区供大家讨论。我们是一个非常大的兴趣团队,虽然参与双生研发的只有寥寥三人,但是在产品脑暴的时候,团队的成员都提出了各自的见解与建议。例如,下图是我们在团队研发中讨论的内容。 [图片] 这里我们团队用的是产品是“语雀”,当然工具是随意的,用腾讯文档我觉得也非常方便,重要的是一定要形成电子版记录材料,如果只是在微信群里讨论或者线下简单聊聊,那讨论了、忘记了,那就相当低效,约等于没有讨论。 1.2 文档整理 第二步,整理脑暴的文档并撰写相关的研发文档,具体来说,包括但不限于: 需求文档 产品文档 模型文档 接口文档 [图片] PS. 这是我们团队的文档库,仅供参考:零熊 | 语雀 [图片] 2. 设计 设计工具我们用的 Sketch,但是不会把源文件直接发给研发同学,因为正版 Sketch 挺贵的,而且只支持 mac 系统。这里我们使用的工具是蓝湖,开始用的也是语雀的画板,但是发现实在是太难用了…另外,在蓝湖中的设计稿是可以分享的,并且邀请团队里的同学进行点评。 设计稿的内容具体包括: 规范 原型 UI 切图 规范重点是色彩规范、组件规范、和字体规范。原型更多的是交互说明,这里我们只是用批注的方式在 ui 上详细说明了一下交互,但如果直接用 flinto 去做也是可以的。flinto 的好处是更加直观,但是开发人员不一定能 get 到设计同学的全部内容。 [图片] 3. 开发 本部分分享的是产品研发的核心环节:项目开发。本环节我分享的内容会稍微多一些,但也略微零散,主要包含三个内容: 规划记录 开发工具 建议事项 3.1 规划记录 在开发之前,我习惯于自己先列一个 todolist 去罗列出项目中的各个需求点或技术点,从整体上会有一个直观的感受,也方便我去安排和规划自己的开发任务。这里我使用的工具是 Notion,我先按照重要的模块把产品分割成了 8 个部分,然后再在每个部分里写各自的 todolist,以免单文件 todolist 过长。 [图片] [图片] 当然,todolist 不单单记录待办事项而已,它更多的是承担一个开发日记的作用。我个人倾向于把开发中遇到的难点问题及解决方法,或者用到的资源顺手记录下来。我认为开发是一个学习和成长的过程,而不仅仅只是完成业务需求。 随手记录是方便日后整理为博客或者再遇到类似的问题可以快速定位,若不记录则很容易忘记。因此,做开发日记对学习的成效是非常大的。 [图片] 3.2 开发工具 针对微信小程序开发,我建议对开发很熟悉的同学可以尝试去使用 VS Code + 扩展 + 真机的模式进行开发,个人觉得这套流程既高效又不会出错。“高效”是 VS Code 自身的高效,而“出错”指的则是模拟器有时候效果与真机不同。 这里顺便安利一下我自己的 VS Code 配置: [图片] 我喜欢把资源管理器放在右边,有两个原因:一是左边是人的注意区,故应该放代码编辑器;二是我随时按 Cmd + B 可以隐藏资源管理器而同时不改变编辑器的位置,如果放左边,隐藏的时候编辑器会有一个位移,眼睛会很不舒服。 对于扩展,我这里用了几个比较有意思的: Color Highlight:颜色值高亮可视化为颜色本身,方便前端样式开发 TODO Highlight:高亮 TODO 与 FIXME miniapp:小程序标签与属性自动补全 Bracket Pair Colorizer:括号着色配对,这个特别方便。 Image preview:方便在代码里预览 uri 上的图片,我是用来看看自己资源路径有没有引错。 REST Client:HTTP 测试,方便开发、分享与 mock。 主题我用的是 Winter is Coming Theme + Material Icon Theme,我个人觉得黑色默认也非常好看。 3.3 注意事项 如果是协同开发我推荐搭配 Git History + Eslint 插件,当然如果自己开发,也免不了 Eslint。Git Commit 规范我们用的是这套: Commit 提交规范 [图片] 开发的时候也别忘记埋点,做一些打点统计,需要打点的地方根据项目需要检测的内容来定。如 PV、UV 这些小程序自带帮你统计了你可以不用打,但其他项目还是要统计的,或者直接规划好 Nginx 的日志,再对日志做分析也是 ok 的~ 如果前后端分离开发,前端同学可以自己接 mockjs 做一套符合接口文档规范的 mock 接口。 4. 维护 对于用户的反馈,我们智能筛选后自动提交到 github issue,再针对 issue 进行 label 和优先级分配。这是我们项目开源的主地址:oh-bear/2life。 [图片] 可以看到 issue 是比较杂乱的,所以还需要 github 的 project 去做一个任务画板。 [图片] by the way,安利一下小工具Devhub,可以很方便的检测自己负责项目的 issue。 [图片] 针对这些 issue,可以做一个阶段性的文档,回归到“立项”步骤,进行下一个小版本的开发。 [图片] 可以发现,我始终没有去选择使用甘特图软件,虽然甘特图更加直观,但是我不太喜欢把任务排的满满的、紧紧的,这样会不自觉地产生工作压力。最重要的是,我们毕竟不是工作嘛,只是一个兴趣开发,所以还是遵循自己的喜好来便好~ 好了,这次的分享就到这里。我是 Airing,我的个人博客是:https://me.ursb.me,欢迎大家来访交流~
2019-05-14 - 小程序需要https域名,不会配置HTTPS?给我5分钟,手把手教你
本文针对不会配置HTTPS或者小白开发着,请开发者社区的大佬们自动忽略。非广告,心得分享,勿喷,谢谢。 👇 推荐一个小程序商城,全开源,码云GVP项目,有兴趣的可以了解一下:【点击下载】 👇 👇 正文开始 01、关于 FreeSSL.cn FreeSSL.cn 是一个免费提供 HTTPS 证书申请、HTTPS 证书管理和 HTTPS 证书到期提醒服务的网站,旨在推进 HTTPS 证书的普及与应用,简化证书申请的流程。 当然了,我看重的不是免费(微笑~),而是 FreeSSL 使用起来非常人性化。我是一个计算机常识非常薄弱的程序员(羞愧一下),但通过 FreeSSL,我竟然可以独自完成 Tomcat 的 HTTPS 配置! 很多年以前,公司要做华夏银行的接口对接,需要 HTTPS 访问,大概花了 3000 块买的证书,最后证书还有问题,HTTPS 也没搞定。总之,坑的很! FreeSSL.cn 有很大的不同,申请非常便捷,优点很多,值得推荐一波。毕竟再也不用邮件、电话各种联系了(也许时代进步了)。 100% 永久免费;这要感谢 Let’s Encrypt 与 TrustAsia 提供的免费 SSL 证书。 在 HTTPS 证书到期前,FreeSSL.cn 会及时地提醒更换证书,免费的服务。 私钥不在网络中传播,确保 HTTPS 证书的安全。 02、使用 FreeSSL 申请证书 第一步,填写域名,点击「创建免费的 SSL 证书」 [图片] 第二步,填写邮箱,点击「创建」 [图片] 1)证书类型默认为 RSA RSA 和 ECC 有什么区别呢?可以通过下面几段文字了解一下。 HTTPS 通过 TLS 层和证书机制提供了内容加密、身份认证和数据完整性三大功能,可以有效防止数据被监听或篡改,还能抵御 MITM(中间人)攻击。TLS 在实施加密过程中,需要用到非对称密钥交换和对称内容加密两大算法。 对称内容加密强度非常高,加解密速度也很快,只是无法安全地生成和保管密钥。在 TLS 协议中,应用数据都是经过对称加密后传输的,传输中所使用的对称密钥,则是在握手阶段通过非对称密钥交换而来。常见的 AES-GCM、ChaCha20-Poly1305,都是对称加密算法。 非对称密钥交换能在不安全的数据通道中,产生只有通信双方才知道的对称加密密钥。目前最常用的密钥交换算法有 RSA 和 ECDHE:RSA 历史悠久,支持度好,但不支持 PFS(Perfect Forward Secrecy);而 ECDHE 是使用了 ECC(椭圆曲线)的 DH(Diffie-Hellman)算法,计算速度快,支持 PFS。 2)验证类型默认为 DNS DNS 和文件验证有什么区别呢?我们再来一起了解下。 首先,我们需要明白一点,CA(Certificate Authority,证书颁发机构) 需要验证我们是否拥有该域名,这样才给我们颁发证书。 文件验证(HTTP):CA 将通过访问特定 URL 地址来验证我们是否拥有域名的所有权。因此,我们需要下载给定的验证文件,并上传到您的服务器。 DNS 验证:CA 将通过查询 DNS 的 TXT 记录来确定我们对该域名的所有权。我们只需要在域名管理平台将生成的 TXT 记录名与记录值添加到该域名下,等待大约 1 分钟即可验证成功。 所以,如果对服务器操作方便的话,可以选择文件验证;如果对域名的服务器操作比较方便的话,可以选择 DNS 验证。如果两个都方便的话,请随意选啦。 3)CSR生成默认为离线生成 离线生成、浏览器生成 和 我有 CSR 又有什么区别呢?来,我们继续了解一下。 离线生成 推荐!!!:私钥在本地加密存储,更安全;公钥自动合成,支持常见证书格式转换,方便部署;支持部分 WebServer 的一键部署,非常便捷。 离线生成的时候,需要先安装 KeyManager,可以提供安全便捷的 SSL 证书申请和管理。下载地址如下: https://keymanager.org/ Windows 的话,安装的时候要选择“以管理员身份运行”。 浏览器生成:在浏览器支持 Web Cryptography 的情况下,会使用浏览器根据用户的信息生成 CSR 文件。 Web Cryptography,网络密码学,用于在 Web 应用程序中执行基本加密操作的 JavaScript API。很多浏览器并不支持 我有 CSR:可以粘贴自己的 CSR,然后创建。 第三步,选择离线生成,打开 KeyManager 填写密码后点击「开始」,稍等片刻。 第四步,返回浏览器,点击「下一步」,出现如下界面。 [图片] 第五步,下载文件,并上传至服务器指定目录下。 第六步,点击「验证」,通过后,出现以下界面。 [图片] 第七步,点击「保存到KeyManager」,可以看到证书状态变成了已颁发。 03、为 Tomcat 配置 jks 格式证书 第一步,导出证书。假如服务器选择的 Tomcat,需要导出 Java keystone (简拼为 jks)格式的证书。 [图片] 注意:私钥的密码在配置 Tomcat 的时候用到。 [图片] 第二步,上传证书至服务器。 第三步,配置 Tomcat 的 server.xml。 [代码] <Connector port="81" protocol="HTTP/1.1" maxThreads="250" maxHttpHeaderSize="8192" acceptCount="100" connectionTimeout="60000" keepAliveTimeout="200000" redirectPort="8443" useBodyEncodingForURI="true" URIEncoding="UTF-8" compression="on" compressionMinSize="2048" noCompressionUserAgents="gozilla, traviata" compressableMimeType="text/html,text/xml,application/xml,application/json,text/javascript,application/javascript,text/css,text/plain,text/json,image/png,image/gif"/> <Connector protocol="org.apache.coyote.http11.Http11NioProtocol" port="443" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" keystoreFile="/home/backup/qingmiaokeji.cn.jks" keystorePass="Chenmo" clientAuth="false" sslProtocol="TLS" useBodyEncodingForURI="true" URIEncoding="UTF-8" compression="on" compressionMinSize="2048" noCompressionUserAgents="gozilla, traviata" compressableMimeType="text/html,text/xml,application/xml,application/json,text/javascript,application/javascript,text/css,text/plain,text/json,image/png,image/gif" /> [代码] 其中 keystorePass 为导出证书时私钥的加密密码。 第四步,重启 Tomcat,并在浏览器地址栏中输入 https://你的域名/ 进行测试。 注意到没,浏览器地址栏前面有一个绿色的安全锁,这说明 HTTPS 配置成功了!好了,为自己鼓个掌! 04、最后 你有没有订个五分钟的时间沙漏?如果超过五分钟 HTTPS 还没有配置成功,你过来揍我!反正你又打不来我!我在CRMEB等你! 👇 👇 👇 最后亿遍,再次发一下我的项目:全开源啊!公众号+小程序啊!商城系统啊!免费啊!了解一下啊→→→点我点我!
2019-05-10