在基于B/S 的业务系统中,如果要设计开发加密解密机制。有几种设计选型:

  • 可以使用现成的HTTPS 架构,后端部署用知名签名机构生成的证书。
  • 可以使用现成的HTTPS 架构,后端部署自签名的证书,但是用户需要在浏览器中导入自签名证书。
  • 基于HTTP 传输加密的数据。也就是说,虽然使用非加密的传输协议,但是数据本身是加密的。

本文说明的加密系统是基于第三个技术选型进行设计,即在使用HTTP 协议进行传输,并对传输数据加密。

系统架构

在该架构中存在一个加密服务,对外提供RESTful的HTTP GET API: /crypto/key, 该API的响应体中包含AES对称加密算法的配置信息,同时包含RSA公钥。AES算法的配置信息在加密服务中使用RSA的私钥进行了加密。加密服务的使用者需要从加密服务/crypto/key获取算法配置信息。该加密系统支持的场景为:

  • 在同一业务系统的前端和后端之间进行加密。
  • 在不同业务系统之间进行加密。

其架构图如下所示:


其主要步骤有:
1)业务系统A 的前端通过HTTP 请求,从加密服务获取加密算法相关信息,并通过封装的驱动文件
解析加密算法信息。
2)业务系统A 的后端通过HTTP 请求,从加密服务获取加密算法相关信息,并通过封装的驱动文件
解析加密算法信息。
3)业务系统A 前端使用AES 加密业务数据,通过HTTP 请求发送到后端。后端接收到前端的加密数据后,使用AES 解密数据,并进行后续的处理。相反的方向是类似的,后端使用AES 加密业务数据,通过HTTP 传输到前端,前端使用AES 解密接收到的后端加密数据,并进行后续的处理。
4)业务系统B 的前端和后端与加密服务进行的交互以及业务系统B 的前后端间的加密传输,和步骤1,2,3 的说明一样,不再重复阐述。
5)业务系统A 和业务系统B 进行交互时,业务系统A 的后端使用AES 加密数据,通过HTTP 传输到业务系统B 后端,业务系统B 后端使用AES 解密数据,并进行后续处理。反方向的流程是类似的处理。

加密服务

加密服务是个web服务,其设计不限于某种程序语言。既可以使用基于Spring boot的java程序设计语言开发也可以使用Node.js平台或其他任何程序设计语言开发。本文使用Spring boot进行设计开发。
加密服务Spring boot的入口程序如下:

@SpringBootApplication
public class CryptoApplication {public static void main(String[] args) {SpringApplication.run(CryptoApplication .class, args);}@Beanpublic CommandLineRunner init(final CryptoController cryptoController) {CommandLineRunner commandLineRunner = (String ...strings) -> {cryptoController.init();};return commandLineRunner;}
}

加密服务controller:

@RestController
public class CryptoController {@Value("${crypto.pubFile}")private String publicKeyFile;@Value("${crypto.prvFile}")private String privateKeyFile;@Value("${crypto.aes.mode}")private String aesMode;@Value("${crypto.aes.key}")private String aesKey;@Value("${crypto.aes.iv}")private String aesIv;private String publicKey;private RSAPrivateKey privateKey;private byte[] aesKeyBytes;private byte[] aesIvBytes;@GetMapping(value = "/crypto/key", produces = "application/json")public ResponseEntity<CryptoKey> getCryptoKey(@RequestHeader String referer) throws Exception {Assert.isTrue (StringUtils.hasText(aesMode));Assert.isTrue (StringUtils.hasText(aesKey));Assert.isTrue (StringUtils.hasText(aesIv));Assert.isTrue (StringUtils.hasText(publicKey));Assert.isTrue (privateKey != null);boolean refererOk = checkReferer(referer)if (!refererOk) {return ResponseEntity.badRequest().build();}Cipher cipher = Cipher.getInstance("RSA");cipher.init(Cipher.ENCRYPT_MODE, privateKey);cipher.update("aes".getBytes(StandardCharsets.UTF_8));byte[] algBytes = cipher.doFinal();cipher.init(Cipher.ENCRYPT_MODE, privateKey);cipher.update(aesMode.getBytes(StandardCharsets.UTF_8));byte[] modeBytes = cipher.doFinal();cipher.init(Cipher.ENCRYPT_MODE, privateKey);cipher.update(aesKeyBytes);byte[] keyBytes = cipher.doFinal();cipher.init(Cipher.ENCRYPT_MODE, privateKey);cipher.update(aesIvBytes);byte[] ivBytes = cipher.doFinal();cipher.init(Cipher.ENCRYPT_MODE, privateKey);ByteBuffer byteBuffer = ByteBuffer.allocate(Long.BYTES);byteBuffer.putLong(System.currentTimeMillis());cipher.update(byteBuffer.array());byte[] versionBytes = cipher.doFinal();CryptoKey cryptoKey = new CryptoKey();cryptoKey.setK(publicKey);cryptoKey.setA(Codec.bytesToBase64String(algBytes));cryptoKey.setM(Codec.bytesToBase64String(modeBytes));cryptoKey.setP(Codec.bytesToBase64String(keyBytes));cryptoKey.setV(Codec.bytesToBase64String(ivBytes));cryptoKey.setT(Codec.bytesToBase64String(versionBytes));return ResponseEntity.ok(cryptoKey);}public void init() {Assert.isTrue (StringUtils.hasText(publicKeyFile));Assert.isTrue (StringUtils.hasText(privateKeyFile));// Load public key.ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(2048);try (InputStream inputStream = new ClassPathResource(publicKeyFile).getInputStream()) {FileCopyUtils.copy(inputStream, byteArrayOutputStream);byte[] bytes = byteArrayOutputStream.toByteArray();publicKey = new String(bytes, StandardCharsets.UTF_8);} catch (Exception e) {logger.error(e.getMessage());return;}// Load private key.try (InputStream inputStream = new ClassPathResource(privateKeyFile).getInputStream();InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);PemReader pemReader = new PemReader(inputStreamReader)) {PemObject pemObject = pemReader.readPemObject();PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(pemObject.getContent());KeyFactory factory = KeyFactory.getInstance("RSA");privateKey = (RSAPrivateKey) factory.generatePrivate(pkcs8EncodedKeySpec);} catch (Exception e) {logger.error(e.getMessage());return;}aesKeyBytes = Codec.base64StringToBytes(aesKey);aesIvBytes = Codec.base64StringToBytes(aesIv);}private boolean checkReferer(String referer) {URI uri = URI.create(referer);String host = uri.getHost();if ("localhost".equals(host)) {return false;}if ("127.0.0.1".equals(host)) {return false;}return true;}
}

需要注意的是:

  • 该API要求校验请求头referer,从而保证请求是来自部署于web服务器中的客户端程序。

CryptoKey 是pojo类,加密服务API的响应体中是该类的JSON格式数据。该类的定义为:

public class CryptoKey {private String k;private String a;private String m;private String p;private String v;private String t;public String getK() {return k;}public void setK(String k) {this.k = k;}public String getA() {return a;}public void setA(String a) {this.a = a;}public String getM() {return m;}public void setM(String m) {this.m = m;}public String getP() {return p;}public void setP(String p) {this.p = p;}public String getV() {return v;}public void setV(String v) {this.v = v;}public String getT() {return t;}public void setT(String t) {this.t = t;}
}

需要说明的是:

  • 加密服务可以随时更新算法相关的信息,如RSA的公钥和私钥,AES的密钥和初始向量。在CryptoKey 类中存在一个版本字段,加密服务每次更新其算法相关信息,该版本字段也同步更新。该机制可以进一步提高加密服务的数据安全。加密服务的使用者必须确认双方加密算法的版本信息一致,否则无法进行正确的加解密。
  • 加密服务需要部署PEM格式的RSA公钥和私钥文件。

客户端驱动文件

客户端是运行在浏览器环境中的JavaScript程序。客户端驱动文件封装了加解密函数,便于客户端直接使用。该驱动文件的实现为:

export const getAesConfig = function(json) {let pubKeyPem = json["k"];let secretKey = json["p"];let algorithm = json["a"];let aesMode = json["m"];let aesIv = json["v"];let version = json["t"];let secretKeyPlain = crypto.publicDecrypt(pubKeyPem, Buffer.from(base64.toByteArray(secretKey)));let aesIvPlain = crypto.publicDecrypt(pubKeyPem, Buffer.from(base64.toByteArray(aesIv)));let algorithmPlain =  crypto.publicDecrypt(pubKeyPem, Buffer.from(base64.toByteArray(algorithm)));let aesModePlain = crypto.publicDecrypt(pubKeyPem, Buffer.from(base64.toByteArray(aesMode)));let versionPlain = crypto.publicDecrypt(pubKeyPem, Buffer.from(base64.toByteArray(version)));return {algorithmPlain, aesModePlain, secretKeyPlain, aesIvPlain, versionPlain};
}// Plain text is String type.
export const encrypt = function(plainText, aesModePlain, secretKeyPlain, aesIvPlain) {let plainTextBytes = aesJs.utils.utf8.toBytes(plainText);// Hard code to use AES-OFB mode.let aesOfb = new aesJs.ModeOfOperation.ofb(secretKeyPlain, aesIvPlain);let cipherTextBytes = aesOfb.encrypt(plainTextBytes);return base64.fromByteArray(cipherTextBytes);
}// Cipher text is encrypted data with base64 encoding.
export const decrypt = function(cipherText, aesModePlain, secretKeyPlain, aesIvPlain) {let cipherTextBytes = base64.toByteArray(cipherText);// Hard code to use AES-OFB mode.let aesOfb = new aesJs.ModeOfOperation.ofb(secretKeyPlain, aesIvPlain);let plainTextBytes = aesOfb.decrypt(cipherTextBytes);return aesJs.utils.utf8.fromBytes(plainTextBytes);
}// Password are string type.
export const encryptPassword = function(password, aesModePlain, secretKeyPlain, aesIvPlain) {let plainData  = password + aesModePlain + base64.fromByteArray(secretKeyPlain) + base64.fromByteArray(aesIvPlain);let cipherData = encrypt(plainData, aesModePlain, secretKeyPlain, aesIvPlain);password = new MD5().update(cipherData).digest('hex');let salt = encrypt(Date.now(), aesModePlain, secretKeyPlain, aesIvPlain);return {password, salt};
}

需要说明的是:

  • 单独封装了密码处理函数encryptPassword ,因为密码通常需要某种哈希进行处理,如MD5, SHA1等,从而保证其不可逆。

下面是一个简单的html页面,用于说明如何在浏览器客户端环境使用驱动文件封装的相关加解密函数。

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>Crypto driver test.</title></head><body><script src="crypto-driver-bundle.js"></script><script>async function test(){let serverUrl = "http://ip:port/crypto/key";let response = await fetch(serverUrl);if (!response.ok) {console.error("Fetch failed.");return;}let body = await response.json();let {algorithmPlain, aesModePlain, secretKeyPlain, aesIvPlain, versionPlain} = cryptoDriver.getAesConfig(body);let {password, salt} = cryptoDriver.encryptPassword("123456", aesModePlain, secretKeyPlain, aesIvPlain);let plainText = "Hello world.";let cipherText = cryptoDriver.encrypt(plainText, aesModePlain, secretKeyPlain, aesIvPlain);let original = cryptoDriver.decrypt(cipherText, aesModePlain, secretKeyPlain, aesIvPlain);if (plainText === original) {console.log("Test OK.");} else {console.log("Test failed.");}}test();</script><p> Crypto driver </p></body>
</html>

需要说明的是:

  • 文件crypto-driver-bundle.js是使用webpack打包客户端驱动文件源代码后的生成文件,对外导出了cryptoDriver对象,其有四个属性,对应加解密相关的四个函数:getAesConfigencryptPasswordencryptdecrypt

服务端的驱动文件

因为JavaScript是浏览器客户端的事实标准,因此需要为客户端提供基于JavaScript封装的驱动文件,但是对于服务端,其程序实现可以是Java,Node.js,Python等其他程序设计语言,为便于服务端使用加密服务,也需要为服务端提供封装的驱动文件,其实现逻辑和客户端驱动文件一致,封装getAesConfigencryptPasswordencryptdecrypt四个加解密函数。

如何基于HTTP设计一个加密解密系统相关推荐

  1. c语言解密pdf,C语言设计-英文加密解密系统资料.pdf

    昆明理工大学 <程序设计基础>课程 综合设计实践教学课题报告 课程名称: C 语言设计 课题名称: 英文加密解密系统 组长: 学号 姓名 组员: 学号 姓名: 学号 姓名 学院: 专业班级 ...

  2. 如何设计一个高性能短链系统?

    目录 前言 短链有啥好处,用长链不香吗 短链跳转的基本原理 短链生成的几种方法 1.哈希算法 如何缩短域名 如何解决哈希冲突的问题? 2.自增序列算法 高性能短链的架构设计 总结 前言 今天,我们来谈 ...

  3. JS基于编码方式实现加密解密文本

    JS基于编码方式实现加密解密文本 严格来讲这是一种简单的编码方式:加密,将明文[注]转成编码.解密则是编码转码为明文本. [注:明文是指没有加密的文字(或者字符串),一般人都能看懂.] 下面源码用到 ...

  4. 简单文件加密解密系统(c++)

    简单文件加密解密系统(c++) 一.原理 二.实现思路 三.c++代码 四.运行结果 一.原理   为了实现加解密,首先需要一个码本文件.一个基本的码本文件包含26个字母,即a~z打乱顺序的结果,比如 ...

  5. 如何设计一个单点登录系统

    本文来说下如何设计一个单点登录系统 文章目录 概述 JWT的组成 头部(Header) 载荷(Payload) 签名(签名) 签名的目的 信息会暴露 JWT的适用场景 用户认证八步走 和Session ...

  6. 小米的开源监控系统open-falcon架构设计,看完明白如何设计一个好的系统

    小米的开源监控系统open-falcon架构设计,看完明白如何设计一个好的系统 小米的http://book.open-falcon.org/zh/intro/ 早期,一直在用zabbix,不过随着业 ...

  7. 基于Java设计一个短链接生成系统

    相信大家在生活中会收到很多短信,而这些短信都有一个特点是链接很短.这些链接背后的原理是什么呢?怎么实现的?小编今天就带你们详细了解一下 我们知道,短信有些是有字数限制的,直接放一个带满各种参数的链接, ...

  8. mysql每秒支持多少并发_如何设计一个高并发系统?

    面试题 如何设计一个高并发系统? 面试官心理分析 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了.为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先. 如果你确 ...

  9. oom 如何避免 高并发_【面试题】如何设计一个高并发系统?

    面试题 如何设计一个高并发系统? 原文链接:https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/high- ...

最新文章

  1. 前端如何使用proxyTable和nginx解决跨域问题
  2. 长得类似铁甲小宝的机器人_铁甲小宝:小时候只顾看机器人忽略重点,长大后再看:是我太天真...
  3. (JAVA)Object类之Scanner
  4. Spring Boot 热部署 devtools模块
  5. Unity的NewInputSystem的InputManager实现多玩家的总结
  6. 深入理解 ASP.NET 动态控件 (Part 5 - 编译实验)
  7. SQL Proc(存储过程)/tran(事物)
  8. python实现一个json文件任意路径形式的接口项目
  9. EC20 和 Air724 4G模组连接OneNet平台笔记
  10. c语言字符串把小写转换大写字母,c语言将字符串中的小写字母转换成大写字母分享...
  11. ZN-M160G光猫 Telnet打不开
  12. 转一篇经典音响文章《“摩机”宝典之如何“摩功放”》
  13. 单片机控制蜂鸣器和弦音发音程序
  14. 【 javascript】JS语法 ES6、ES7、ES8、ES9、ES10、ES11、ES12新特性
  15. 计算机类毕业设计优秀最新题目
  16. 从产物追溯研发合成路线?自建化合物数据库溯源不再成难题
  17. K8S相同后端存储在2个K8S集群PVC数据直接拷贝
  18. 如何下载安装和使用 Office 2016的中文语言包?
  19. 爱因斯坦曾出过这样一道数学题:有一条长阶梯,若每步跨2阶,最后剩下1阶;若每步跨3阶,最后剩下2阶;若每步跨5阶,最后剩下4阶;若每步跨6阶,则最后剩下5阶;只有每步跨7阶,最后才正好1阶不剩。参考例
  20. qt vs调试pdb文件下载

热门文章

  1. linux 桌面 修复工具下载,恢复ubuntu20.04默认桌面管理器
  2. 浅析正向代理、反向代理、透明代理
  3. 中国人民解放军郑州计算机学院官网,解放军信息工程大学录取分数线2021
  4. dw编写html缩小间距,dw段落行距怎么设置 用DW怎么控制文章的行间距
  5. Data Structures in C++:八大基本数据结构概述
  6. 【Spring】IoC与AOP
  7. CTF-Anubis HackTheBox 渗透测试(二)
  8. TestBird《2021中国手游测试白皮书》---国内手游
  9. b站如何一次性把up主全部取消关注,让自己去学习
  10. 数据挖掘Task 5: 模型融合