# 加密解密

本文档对V8.1及更高版本有效,自V8.1版本开始,平台对加解密进行了升级,以下是对新算法的说明!

# 修订记录

修订内容 修订时间
完善传输加密章节内容(内部关联单号JSFW-2024-02672) 2024年12月

# 功能简介

标准产品主要做了2次大的算法升级(或新增):

  • 第一次升级:V8.1针对数据存储加解密算法进行了升级,由旧的国际算法升级为“新国际算法”、“国密算法”、“加密机算法”三选一,以满足不用客户需求。
  • 第二次升级:V8.1SP1针对部分隐私数据增加了加密传输算法,以满足基本的加密传输合规性

# Ⅰ数据保护范围(since V8.1)

第一次升级:V8.1数据存储加解密升级,废弃了老旧的SHA-1、DES算法,升级为更安全的新算法。

除此之外,还扩展了数据存储加密领域,让更多关键数据支持加密存储,主要完成以下变化:

算法|业务 密码|数据签名 附件 日志 用户隐私信息 正文
旧国际算法(8.0SP2及更早) SHA-1 DES
新国际算法(8.1以后三选一) SHA-256 AES-128 AES-128 AES-128 AES-128
内置国密算法(8.1以后三选一) SM3 SM4 SM4 SM4 SM4
加密机算法(8.1以后三选一) 三未信安SM3 三未信安SM4 三未信安SM4 三未信安SM4 三未信安SM4
中度加密算法(适用所有版本) 异或算法

注:加密机算法依赖三方密码机设备,需采购对应型号密码机集成才能生效,详细见对应版本的发版文档-安装维护手册-外接国密加密机(三未信安)配置手册。

数据保护即指定敏感数据加密存储到数据库。敏感数据在写入和读取时存在加解密操作,故加密数据比不加密数据更耗时,请根据安全保密级别按需开启加密控制。

数据保护范围内容有:密码、附件、正文、隐私信息(用户身份证号、办公电话、手机电话、电子邮箱等文本)、应用日志、数据完整性校验、工资。

随着产品持续发展,用户需求的变化,数据保护范围也会相应变化扩大,不同版本支持的范围可能存在差异性。数据保护范围全部可配置,通过系统管理员角色查看和配置加密范围:安全管理>数据保护>数据加密做个性化配置!

1694145266378.png


关于表单数据加密,标准产品无法做存储加密,主要原因是表单字段存在多样化,全面加密后,即时加密和即使解密能力都会遇到巨大瓶颈,系统体验就是加载慢、卡,故不适合做大面积加密。如有需求,可报成本,走项目化开发支持。

# Ⅱ加密传输(since V8.1SP1)

第二次升级:V8.1SP1针对部分隐私数据增加了加密传输算法,以满足基本的加密传输合规性。

应用价值:在等保/分保/密评合规要求中,需要保障重要业务数据、个人敏感信息存储、传输的机密性和完整性要求,标准产品提供了加密传输的功能和接口能力。

功能特性:传输机密性,传输(从前端浏览器、M3、等传到后台)要加密传输,传输完整性:验签。

依赖插件:分保插件。

标准化支持内容:人员信息敏感字段(办公电话、手机号码、电子邮件),在传输过程中数据通过加密传输,防止被拦截破解。

功能开关位置:通过系统管理员角色访问:安全管理>数据保护>加密传输。传输默认关闭。

1698739846728.png

传输加解密的运行逻辑图如下,关键逻辑为:

  • 客户端在发送关键数据时,先从服务器获取国密SM2公钥(该算法专用于快速生成密钥)
  • 客户端再使用SM2颁发的公钥结合客户端SDK进行加密,将加密后的密文传输到服务器
  • 服务器同步解密获得正确的数据,再处理后续业务逻辑

1734954132618.png

全过程涉及加、解、完整性校验等多个操作,详细流程图如下:

1734954392112.png

# 扩展阅读:关于国密

本次主要是引入国密算法(中华人民共和国自主知识产权的数字密码学算法),简介如下:

国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。

  • SM1 为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
  • SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。
  • SM3 消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。类似算法还有SHA。
  • SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。

由于SM1、SM4加解密的分组大小为128bit,故对消息进行加解密时,若消息长度过长,需要进行分组,要消息长度不足,则要进行填充。

# 技术使用说明

# 部分算法示意图

# 摘要算法示意图

默认采用SM3,常用于密码存储。

1734954672385.png

# 对称加解密算法示意图

密钥长度32位,采用ecb模式加密,默认使用SM4,常用于日志、正文、隐私信息存储(查看时解密)。

1734954633720.png

# Ⅰ加解密接口

业务需求是通过系统配置可以自由的切换加密算法。为了达到高内聚的效果,我们将通过开关切换算法的逻辑添加到平台内。业务只需要通过工厂方法获取算法接口的实现即可。

平台只提供加密/解密/加签/验签能力,其中凭证ID即为加签操作返回的签文,主要支持对称加密算法(如:SM4)和哈希算法(如:SHA-1\SM3)前者是可逆操作如:加密用户身份信息、文件等,后者不可逆如:用户密码、凭证校验。

如果你不知道加密、解密、加签、验签,建议先学习相关概念再往下看。

EncryptCoder具体方法如下:

方法名称 传入参数 返回参数 说明
encrypt byte[] byte[] 文件的加密方法,传入文件的byte数组返回加密后的byte数组,byte[]可用通过InputStream().getBytes()获取
encrypt String String 字符串类型的加密方法,传入字符串明文,返回加密后的字符串密文
encrypt Double data:待加密数字
long seed:参与混肴的数字盐
Double 浮点树的加密方法采用混淆算法,传入待加密的浮点数data和参与混肴的数字盐往往是用户的id
decrypt byte[] ciphertext:带解密的密文数组 byte[]:解密后的明文字节数组 文件类型的解密方法,传入加密文件的字节数组,返回解密后文件的字节数组
decrypt String ciphertext:待解密的密文字符串 String:解密后的明文串 字符串类型的解密方法,传入加密的字符串,返回解密后的明文字符串
decrypt Double data:待解密数字
long seed:参与混肴的数字盐
int decimal:保留的小数位
Double:解密后的数字 数字类型的解密方法,参数带解密的数字,混肴的数字盐以及保留的小数位,返回解密后的数字
signature byte[] data:待加签的文件字节数组 String:加签的签文 文件类型的加签方法,传入文件的字节数组吗,返回前文字符串(凭证Id),长度稳定为44个字符
signature String data:签名的字符串
String seed:参与签名算法的盐,非必需
String:加签的签文 字符串类型的加签方法,传入待签名字符串以及签名的盐(此参数保证同样的字符处理后的签文不一致),返回签文
signatureCheck byte[] data:验签数据
String signature:签文
boolean:true-验证成功
false-验证失败
文件类型验签方法,传入做签名的文件字节数组和签文,返回验证结果
signatureCheck String data:签名的字符串
String seed:参与签名算法的盐,非必需
String signature:签文
boolean:true-验证成功
false-验证失败
字符串类型验签方法,传入做签名的字符串和签文,返回验证结果

EncryptCoder接口源码:

public interface EncryptCoder {
	/**
	 * 加密文件类型的二进制数据
	 *
	 * @param data 待加密明文
	 * @return 加密后密文
	 */
	byte[] encrypt(byte[] data) throws CoderException;


	/**
	 * 加密字符串
	 *
	 * @param data 待加密明文
	 *             具体的加密操作:正文/附件/日志等
	 * @return
	 */
	String encrypt(String data) throws CoderException;

	/**
	 * 加密小数
	 *
	 * @param data 待加密数字
	 * @param seed 参与混肴的盐,通常是用户id
	 * @return
	 * @throws CoderException
	 */
	Double encrypt(Double data, long seed) throws CoderException;


	/**
	 * 解密数据 文件
	 *
	 * @param ciphertext 密文
	 * @return 解密后明文
	 */
	byte[] decrypt(byte[] ciphertext) throws CoderException;

	/**
	 * 解密数字
	 *
	 * @param ciphertext 待解密数字
	 * @param seed       参与混肴的盐,通常是用户id
	 * @param decimal    保留的有效小数位
	 * @return
	 * @throws CoderException
	 */
	Double decrypt(Double ciphertext, long seed, int decimal) throws CoderException;

	/**
	 * 解密数据 字符串
	 *
	 * @param ciphertext 密文
	 * @return 解密后的明文串
	 */
	String decrypt(String ciphertext) throws CoderException;


	/**
	 * 文件类型加签
	 *
	 * @param data 待签名的原始数据
	 * @return 签名字符串
	 */
	String signature(byte[] data) throws CoderException;

	/**
	 * 文件验签
	 *
	 * @param data
	 * @param signature
	 * @return
	 */
	boolean signatureCheck(byte[] data, String signature) throws CoderException;

	/**
	 * 字符串签名
	 *
	 * @param data 签名数据
	 * @param seed 盐 保证同样字符串签名后的密文不一样之前是用户的用户登录名加密
	 * @return
	 */
	String signature(String data, String seed) throws CoderException;

	/**
	 * @param data       待签名数据
	 * @param signature 签名后的密文
	 * @param seed       盐 保证同样字符串签名后的密文不一样
	 * @return
	 */
	boolean signatureCheck(String data, String signature, String seed) throws CoderException;
}

目前提供的EncryptActionEnum如下:

	/**
	 * 加密密码
	 */
	ENCRYPT_PWD(EncryptActionVO::getUserPassword),

	/**
	 * 加密附件
	 */
	ENCRYPT_ATTACHMENT(EncryptActionVO::getAttachment),

	/**
	 * 加密正文
	 */
	ENCRYPT_TEXT(EncryptActionVO::getText),

	/**
	 * 加密隐私信息:用户身份证号、办公电话、手机电话、电子邮箱等文本
	 */
	ENCRYPT_SECRECY(EncryptActionVO::getSecrecy),

	/**
	 * 加密应用日志
	 */
	ENCRYPT_APP_LOG(EncryptActionVO::getAppLog),

	/**
	 * 加密数据完整性校验
	 */
	SIGNATURE_DATA(EncryptActionVO::getSignatureData),

	/**
	 * 加密工资:数字
	 */
	ENCRYPT_SALARY(EncryptActionVO::getSalary),

# 加密、加签示例

加密加签时候通过 encryptCoderFactory.getByEncryptActionEnum获取到EncryptCoder。其中每个配置项都与EncryptActionEnum的枚举存在唯一对应关系,因此在做加密/加签操作时候需要传入EncryptActionEnum,平台会去读数据加密配置返回合适的EncryptCoder,如果设置为不加密会返回EncryptCoderEmptyImpl即do nothing(传入什么参数就返回什么参数)。

/**可逆加密身份证号码示例**/
EncryptCoderFactory encryptCoderFactory = EncryptCoderFactory.getInstance();
//身份证属于隐私信息对应ENCRYPT_SECRECY
EncryptCoder encryptCoder = encryptCoderFactory.getByEncryptActionEnum(EncryptActionEnum.ENCRYPT_SECRECY);
//调用encrypt方法加密身份证信息
String encrypt = encryptCoder.encrypt("5132118518445451851");
/**不可逆对用户密码生成签名示例**/
EncryptCoderFactory encryptCoderFactory = EncryptCoderFactory.getInstance();
//用户密码对应操作枚举ENCRYPT_PWD
EncryptCoder encryptCoder = encryptCoderFactory.getByEncryptActionEnum(EncryptActionEnum.ENCRYPT_PWD);
//调用signature方法加密用户密码,加盐是为了保证即使输入一样的密码加密出来的密文也是不一致的
String encrypt = encryptCoder.signature("Seeyon999","username");
/**不可逆对文件生成摘要签名,用于对文件进行防窜改验证**/
//将文件转字节数组
byte[] fileBytes = IOUtility.toByteArray(new FileInputStream(file));
EncryptCoderFactory encryptCoderFactory = EncryptCoderFactory.getInstance();
//无论什么类型,只要是加签操作对应枚举为SIGNATURE_DATA
EncryptCoder encryptCoder = encryptCoderFactory.getByEncryptActionEnum(EncryptActionEnum.SIGNATURE_DATA);
//调用signature生成文件摘要
String encrypt = encryptCoder.signature(fileBytes);

# 解密、验签示例

由于平台加密算法是可配置的,支持不同加密算法的切换(国际通用算法->国密算法->加密机算法),因此在解密/验签操作时候获取EncryptCoder的过程会略有不同。

解密时候通过encryptCoderFactory.getByEncryptActionEnum.getByCipher获取EncryptCoder

/**可逆解密身份证号码示例,与前面加密配套使用**/
EncryptCoderFactory encryptCoderFactory = EncryptCoderFactory.getInstance();
//通过密文解析到做加密时候的算法实现
EncryptCoder decryptCoder = encryptCoderFactory.getByCipher(encrypt);
//解密数据
String decrypt = decryptCoder.decrypt(encrypt);

如果是验签操作同样是通过encryptCoderFactory.getByEncryptActionEnum.getByCipher获取EncryptCoder

/**对用户密码验签示例,与签名密码加密配套使用**/
EncryptCoderFactory encryptCoderFactory = EncryptCoderFactory.getInstance();
//通过密文解析到做加签时候的算法实现
EncryptCoder decryptCoder = encryptCoderFactory.getByCipher(signature);
//验签数据,true为通过,false为不通过
boolean result = decryptCoder.signatureCheck("Seeyon999",signature,"username");
/**对文件进行验签示例,用于验证文件是否被窜改**/
EncryptCoderFactory encryptCoderFactory = EncryptCoderFactory.getInstance();
//通过密文解析到做加签时候的算法实现
EncryptCoder decryptCoder = encryptCoderFactory.getByCipher(signature);
//验签数据,true为通过,false为不通过
boolean result = decryptCoder.signatureCheck(fileBytes,signature);

# Ⅰ加解密算法实现原理

目前的算法实现按照分类来说有:

  • 摘要算法:用于用户密码加密、生成数字签名(凭证id);只可加密不可解密,它所谓的“解密”过程叫“验签”,即将加密后的内容“签文”与需要验证的数据拿去生成的数字签名做对比,如果两者内容一致着验签正确。
  • 对称加密算法:用于用户隐私信息、文件、日志等需要解密出明文的操作。加密/解密过程需要引入一把密钥,加密和解密都需要使用这把密钥才能成功进行。也就是看起来加密和机密过程密钥是一致的成为对称加密。这种加密方式适合只有一方系统内部信息的加密,不适合多个系统传输数据加密解密,因为密钥在传递和多个系统中存储增加丢失几率,对两边系统都产生安全风险。

# 摘要类算法实现方式

摘要算法采用了java自带的MessageDigest,只需要传入算法名称和算法发提供者获取实例,签名代码如下:

	@Override
	public String signature(byte[] data) throws CoderException {
		try {
            //算法名称为SM3,算法供应商provider=SwxaJCE(三位信安),不传递第二个参数(provider)则使用jdk内置的算法
			MessageDigest messageDigest = MessageDigest.getInstance("SM3", "SwxaJCE");
            //加密数据
			messageDigest.update(data);
            //拿到加密结果
			byte[] output = messageDigest.digest();
            //获取该算法的标识内容
			byte[] headerMark = getHeaderMark();
            //将标识添加到签文头部返回最终结果
			return Base64.getEncoder().encodeToString(mergeBytes(headerMark, output));
		} catch (Exception e) {
			LOG.error(e);
			throw new CoderException("use MessageDigest signature failed!");
		}
	}

摘要类算法需要实现getHeaderMark、signature、signatureCheck几个方法即可。实例SHA-256算法实现如下:

public class EncryptCoderSha256 extends AbstractEncryptCoder {

	/**
	 * 日志
	 */
	private static final Log LOG = CtpLogFactory.getLog(EncryptCoderSha256.class);

	/**
	 * 算法名称
	 */
	private static final String ALGORITHM = "SHA-256";

	@Override
	byte[] getHeaderMark() {
		return str2Bytes(EncryptAlgorithmEnum.SYS_SHA256.getValue());
	}

	@Override
	public String signature(byte[] data) throws CoderException {
		try {
			MessageDigest digest = MessageDigest.getInstance(ALGORITHM);
			digest.update(data);
			byte[] output = digest.digest();
			return Base64.getEncoder().encodeToString(mergeBytes(getHeaderMark(), output));
		} catch (Exception e) {
			LOG.error(e);
			throw new CoderException(String.format("use MessageDigest[%s] signature failed!", ALGORITHM));
		}
	}

	@Override
	public boolean signatureCheck(byte[] data, String signature) throws CoderException {
		String current = signature(data);
		return Objects.equal(signature, current);
	}

	@Override
	public String signature(String data, String seed) throws CoderException {
		try {
			MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM);
			//seed不为空则将seed加入到摘要算法中
			if (Strings.isNotBlank(seed)) {
				byte[] seedBytes = messageDigest.digest(str2Bytes(seed));
				messageDigest.update(seedBytes);
			}
			byte[] output = messageDigest.digest(str2Bytes(data));
			return Base64.getEncoder().encodeToString(mergeBytes(getHeaderMark(), output));
		} catch (Exception e) {
			LOG.error(e);
			throw new CoderException(String.format("use MessageDigest[%s] signature failed!", ALGORITHM));
		}
	}

	@Override
	public boolean signatureCheck(String data, String signature, String seed) throws CoderException {
		String current = signature(data, seed);
		return Objects.equal(signature, current);
	}
}

指的注意的是,加签密码时候需要额外传入第二个参数seed,这是为了保证每位用户加签出来的密码都是独一无二的避免被暴力破解,因为密码的安全是系统安全的基础,即使别人得到的密码的密文也无法轻松破解出密码原文。

# 对称类算法实现方式

对称类算法主要用到Cipher通过通过其getInstance方法获取到算法,第一个参数为算法名称,第二参数如果传入则为算法的提供应商如SwxaJCE,计算过程通过doFinal或者update都可以,区别是doFinal是一次性运算、update则是批量多次运算每次都会返回运算的结果。由于需要密钥,因此在构造方法中需要指的密钥的初始化方式和密钥的获取方式。初始化方式是为了系统首次使用该算法时候生成并保该密钥,这样能保证每个OA客户端的每把密钥都是独一无二的。这类算法实现需要实现的方法有:getHeaderMark、encrypt、decrypt 。示例代码如下(AES-128实现)

public class EncryptCoderAESImpl extends AbstractEncryptCoder {

	/**
	 * 日志类
	 */
	private static final Log LOGGER = CtpLogFactory.getLog(EncryptCoderAESImpl.class);


	/**
	 * 算法名称
	 */
	private static final String ALGORITHM = "AES/ECB/PKCS5padding";

	/**
	 * 密钥号
	 */
	private static final String KEY_ID = "**";

	/**
	 * 系统内置的AES算法 加密专用
	 */
	private static Cipher AES_CIPHER_ENCODE;

	/**
	 * 系统内置的AES算法 解密专用
	 */
	private static Cipher AES_CIPHER_DECODE;


	@Override
	public byte[] getHeaderMark() {
		return str2Bytes(EncryptAlgorithmEnum.SYS_AES.getValue());
	}

	@Override
	public byte[] encrypt(byte[] data) throws CoderException {
		try {
			byte[] encode = AES_CIPHER_ENCODE.doFinal(data);
			return mergeBytes(getHeaderMark(), encode);
		} catch (Exception e) {
			LOGGER.error(e);
			throw new CoderException("AES encrypt string failed");
		}

	}

	@Override
	public String encrypt(String data) throws CoderException {
		try {
			byte[] encrypt = encrypt(str2Bytes(data));
			return Base64.getEncoder().encodeToString(encrypt);
		} catch (Exception e) {
			LOGGER.error(e);
			throw new CoderException("AES encrypt string failed");
		}
	}

	@Override
	public String decrypt(String ciphertext) throws CoderException {
		try {
			byte[] decode = Base64.getDecoder().decode(ciphertext);
			decode = removeHeader(decode);
			byte[] doFinal = AES_CIPHER_DECODE.doFinal(decode);
			return bytes2Str(doFinal);
		} catch (Exception e) {
			LOGGER.error(e);
			throw new CoderException("AES decrypt string failed");
		}
	}

	@Override
	public byte[] decrypt(byte[] ciphertext) throws CoderException {
		try {
			//去掉头
			ciphertext = removeHeader(ciphertext);
			//解密
			return AES_CIPHER_DECODE.doFinal(ciphertext);
		} catch (Exception e) {
			LOGGER.error(e);
			throw new CoderException("AES decrypt string failed");
		}
	}


	public String generateKey() throws CoderException {
		try {
			//生成key
			Security.addProvider(new BouncyCastleProvider());
			//生成key
			KeyGenerator keyGenerator=KeyGenerator.getInstance("AES", "BC");
			//AES>=128
			keyGenerator.init(128);
			SecretKey secretKey = keyGenerator.generateKey();
			byte[] key = secretKey.getEncoded();
			return Base64.getEncoder().encodeToString(key);
		} catch (Exception e) {
			LOGGER.error("init AES key-128 failed", e);
			throw new CoderException("init AES key-128 failed");
		}
	}


	/**
	 * 初始化内置的des算法
	 */
	public EncryptCoderAESImpl() throws CoderException {
		try {
			String secretKey = getSecretKey(KEY_ID, this::generateKey);
			//加密
			AES_CIPHER_ENCODE = Cipher.getInstance(ALGORITHM);
			byte[] keyBates = Base64.getDecoder().decode(secretKey);
			SecretKeySpec keySpec = new SecretKeySpec(keyBates, "AES");
			AES_CIPHER_ENCODE.init(Cipher.ENCRYPT_MODE, keySpec);
			//解密
			AES_CIPHER_DECODE = Cipher.getInstance(ALGORITHM);
			AES_CIPHER_DECODE.init(Cipher.DECRYPT_MODE, keySpec);
		} catch (Exception e) {
			LOGGER.error("init AES algorithm failed", e);
			throw new CoderException("init AES algorithm failed");
		}
	}
}

可以看到加密的cipher类和解密的cipher类是分开的,这是因为执行加密和解密cipher的执行模式是不一样的,为了保证线程安全以及性能将加密和解密的cipher分开。同事指定了生成key的方式,如果获取key(getSecretKey(KEY_ID, this::generateKey);)失败则会生成一把key并保存到数据库中。

# 其它代码

# SM2生成密钥代码

1734955496255.png

# 扩展阅读&常见问题

问题:我已经知道密码的加密算法,是否可以使用网上对应加密算法的破解工具来破解加密内容?

回复:公网工具无法破解。加密、解密、验签等操作均需要读产品中的代码,或调用产品中相关加解密代码才能实现。在进行加解密时,我们追加了一些影响因子,比如用户密码加密做了加盐操作,你得知道我们的盐是啥。


以下是研发过程中总结的笔记,方便后续开发接入新算法时参考。

Q1:如何兼容老数据?

将所有老算法通过抽取、提炼出`EncryptCoder`实现中,并配置相关算法枚举到`AlgorithmEnum`中,让老数据也能走新设计下的解密流程:将密文传入`EncryptCoderFactory`,通过解析密文开头标识在`AlgorithmEnum`中匹配到算法实现完成解密

Q2:如何解决多业务数据的加密,并保证一定的扩展性?

业务数据是否加密以及具体的加密算法是通过系统配置中度加密存储来控制的,也就是业务的加密算法的开关以及加密算法具体实现的联动是动态的,因此这部分内容是保存在数据库中的,而为了满足拓展性达需要设计成“动”、“静”分明的效果。

`动`是指的加密存储的配置项、每个加密业务操作的具体实现算法、以及扩展新加密业务的方式是变化的,这部分代码尽量设计成配置式。通过`actionEnum`绑定加密业务操作类型,通过`actionEnum`的枚举属性完成加密存储配置值的映射,以及每个业务加密操作的算法名称映射,最终实现业务通过`actionEnum`->获取算法开关配置&拿到算法名称id->通过`AlgorithmEnum`拿到算法实现。拓展新的业务数据加密操作时候只需要新增一个`actionEnum`以及对应新增`actionEnum`的算法映射配置到数据库即可完成拓展。

`静`是指的面向业务的核心代码是通用始终不变的,即`encryptFactory`工厂类的代码始终不会因为业务功能的拓展而被迫发生改变。因为`encryptFactory`的任意一行代码的修改都会影响所有适配的数据加密的业务,进而规避各种风险的产生。

Q3:如何一种业务数据采用不同加密算法的切换,具备一定的算法扩展性?

业务数据的加密算法切换以及开关控制是通过系统配置中度加密存储来配置的,业务在加密的时候只需要传入`actionEnum`,由平台去查询`actionEnum`对应映射的加密存储配置的算法以及开关的状态,如果加密操作选项为关闭状态则返回内置的`EmptyCoderImpl`这个算法实现中在加密/解密时候执行空操作,对于业务消费方是无感的。业务不需要写判断逻辑是控制数据是否需要加密由平台统一控制。解密时候`encryptFactory`会通过解析密文的头部数据反向查找匹配该信息的算法实现,这样就能保证算法切换后仍然能够成功的机密出来,因为加密密文中存储了算法信息。

因为采用动静分明的设计,扩展新算法时只需要实现`EncryptCoder`接口,并将算法实现添加到`AlgorithmEnum`,最后将替换掉数据库中`actionEnum`对应的算法映射关系数据即可。下次加密时候,通过`actionEnum`找到算法名称id,并在`AlgorithmEnum`中匹配到新的算法实现

Q4:如何支持大于一个加密厂商接入,并具备一定的新厂商接入扩展性?

加入加密的过程从编码层面看只是将算法替换为对于厂商的过程,使用的`java`类仍旧是`javax.crypto.*`下的类。因此本质上来说与扩展新的`jdk`自带国际算法是同样的过程,因此只需实现加密机厂商的算法实现并配置到`AlgorithmEnum`和系统配置文件中即可。
加密机的适配过程往往需要:
1.引入厂商扩展的jar到`jre/ext/lib`下
2.修改相关配置文件让`jdk`加载到扩展算法
3.配置厂商提供的配置文件将加密机的`ip`和密码保证连接的安全,因此能保证一定的扩展性

Q5:如何实现加密机厂商连接模拟测试

由于加密厂商的`sdk`并不会提供测试连接的`api`,并且配置的`ip`也是在`jdk`目录配置的,能`ping`通不一定能连接。因此在连接测试中编写了一段加密代码能成功执行则说明能执行成功并且在设定了线程超时时间,指定时间内未能执行完毕则视为连接失败
创建人:het
修改人:het