第三方测试-系统安全测试问题
现在国内越来越重视系统安全,最近我们公司在做第三方测试过程中对系统安全、系统性能等做出了严格的要求,并进行了很大的整改,就从这篇文章开始总结一下最近三个月以及以后还会继续整改的涉及系统安全的相关测试漏洞的修改方案,相信这些问题会帮助到大家,尤其是比较大型的政府项目,对系统安全更加注重。
在互联网和新兴技术高速发展的今天,数据信息充斥在各行各业中,并发挥着重要的作用。然而,在享受信息化时代带来便利的同时,数据安全问题也成为大家关注的焦点。无论是从toG、toB、toC的各业务场景来看,还是从网络安全(CyberSecurity)的架构来看,数据安全(DataSecurity)都是一个主要的组成部分,而且在新兴技术日新月异的数据时代变得越来越重要,范围也越来越大。
------- 引自 《数据安全架构设计与实战》
安全架构5A方法论
无论是进行产品的安全架构设计或评估,还是规划安全技术体系架构的时候,都有几个需要重点关注的逻辑模块,它们可以在逻辑上视为安全架构的核心元素。
以应用/产品为例,核心元素包括:
■ 身份认证(Authentication):用户主体是谁?
■ 授权(Authorization):授予某些用户主体允许或拒绝访问客体的权限。
■ 访问控制(Access Control):控制措施以及是否放行的执行者。
■ 可审计(Auditable):形成可供追溯的操作日志。
■ 资产保护(Asset Protection):资产的保密性、完整性、可用性保障
关于数据安全方面
我们在测试过程中涉及到了对数据传输加密、数据存储加密、对称加密、非对称加密、SM2、SM3、SM4国密加密,CA证书等等
什么是非对称加密呢?
非对称加密是一种密码学技术,与传统的对称加密不同,它使用一对密钥来进行加密和解密操作,这对密钥分别称为公钥和私钥。这两个密钥是数学上相关联的,但却不能通过已知一个密钥来轻松地推导出另一个密钥。
我们可以想象一下,就像你有一把锁和一把钥匙,任何人都可以得到这把锁,但只有你有这把唯一的钥匙。任何人都可以使用这把锁将信息锁住,但只有你能够使用你的独特的钥匙来解锁这个信息。
- 公钥: 就像是一个开放的锁,任何人都可以获得。它用于加密信息,只有拥有与之相关的私钥的人才能解密。
- 私钥: 这是与公钥相关联的唯一的解锁密钥,只有密钥的拥有者才能解密用公钥加密的信息。
举例来说,考虑用户密码的存储问题。通过非对称加密的方式,可以将用户密码加密后写入数据库。当用户再次登录时,系统将客户端输入的明文密码使用相同的非对称加密方式加密,然后与数据库中的密文进行比对。这样的设计大大提高了安全性,即使数据库被攻击,攻击者也无法轻松获取用户的明文密码。
国密SM2加密方式就是非对称加密的。下面是国密SM2加密工具的源码:
/*椭圆曲线非对称加密算法*/
public class SM2Util {
public static final String key_pubk= "pub";
public static final String key_prik= "pri";
private static SM2 sm2 = SM2.instance();
private static final String DEFAULT_PRIVATE_KEY = "27cd96b1500f8330fc523e7c47ef02a";
private static final String DEFAULT_PUBLIC_KEY = "047ec86bb18f57714e6c5c72383c5b122";
/**
* 生成公私钥
* @return
*/
public static Map<String,String> generateKeyPair() {
Map<String,String> result = new HashMap<>();
AsymmetricCipherKeyPair key = sm2.eccKeyPairGenerator.generateKeyPair();
BigInteger privateKey = ((ECPrivateKeyParameters)key.getPrivate()).getD();
ECPoint publicKey = ((ECPublicKeyParameters)key.getPublic()).getQ();
String pubk = new String(Hex.encode(publicKey.getEncoded(false)), StandardCharsets.UTF_8);
String prik = new String(Hex.encode(privateKey.toByteArray()), StandardCharsets.UTF_8);
result.put(key_pubk, pubk);
result.put(key_prik, prik);
return result;
}
/**
* 获取默认公钥
* @return
*/
public static String getDefaultPublicKey() {
return DEFAULT_PUBLIC_KEY;
}
/**
* 获取默认私钥
* @return
*/
public static String getDefaultPrivateKey() {
return DEFAULT_PRIVATE_KEY;
}
/**
* 加密
* @param data
* @param publicKey
* @return
*/
protected static byte[] encrypt(String data, byte[] publicKey) {
if (StringUtils.isBlank(data)) {
return null;
}
SM2Cipher cipher = new SM2Cipher();
// C1
byte[] c1Bytes = new byte[65];
ECPoint c1 = cipher.encryptInit(sm2, sm2.eccCurve.decodePoint(publicKey));
c1Bytes = c1.getEncoded(false);
// C2
byte[] c2Bytes = data.getBytes(StandardCharsets.UTF_8);
cipher.encrypt(c2Bytes);
// C3
byte[] c3Bytes = new byte[32];
cipher.doFinal(c3Bytes);
byte[] encryptData = new byte[c1Bytes.length + c2Bytes.length + c3Bytes.length];
System.arraycopy(c1Bytes, 0, encryptData, 0, c1Bytes.length);
System.arraycopy(c2Bytes, 0, encryptData, c1Bytes.length, c2Bytes.length);
System.arraycopy(c3Bytes, 0, encryptData, c1Bytes.length + c2Bytes.length, c3Bytes.length);
return encryptData;
}
/**
* 加密
* @param data
* @param publicKey
* @return
*/
public static byte[] encrypt(String data, String publicKey) {
return encrypt(data, HexUtil.hexToByte(publicKey));
}
/**
* 加密
* @param data
* @param publicKey
* @return
*/
public static byte[] encrypt(byte[] data, String publicKey) {
return encrypt(new String(data, StandardCharsets.UTF_8), HexUtil.hexToByte(publicKey));
}
/**
* 加密
* @param data
* @param publicKey
* @return
*/
public static String encryptToHexString(String data, String publicKey) {
return HexUtil.byteToHex(encrypt(data, HexUtil.hexToByte(publicKey)));
}
/**
* 加密
* @param data
* @param publicKey
* @return
*/
public static String encryptToHexString(byte[] data, String publicKey) {
return HexUtil.byteToHex(encrypt(new String(data, StandardCharsets.UTF_8), HexUtil.hexToByte(publicKey)));
}
/**
* 解密
* @param encryptedData
* @param privateKey
* @return
*/
protected static byte[] decrypt(byte[] encryptedData, byte[] privateKey) {
if (ArrayUtils.isEmpty(encryptedData)) {
return null;
}
SM2Cipher cipher = new SM2Cipher();
// C1
byte[] c1Bytes = new byte[65];
System.arraycopy(encryptedData, 0, c1Bytes, 0, c1Bytes.length);
ECPoint c1 = sm2.eccCurve.decodePoint(c1Bytes).normalize();
// C3
byte[] c3Bytes = new byte[32];
System.arraycopy(encryptedData, encryptedData.length - 32, c3Bytes, 0, 32);
// C2
int c2Len = encryptedData.length - 65 - 32;
byte[] c2Bytes = new byte[c2Len];
System.arraycopy(encryptedData, 65, c2Bytes, 0, c2Len);
cipher.decryptInit(new BigInteger(1, privateKey), c1);
cipher.decrypt(c2Bytes);
cipher.doFinal(c3Bytes);
return c2Bytes;
}
/**
* 解密
* @param encryptedData
* @param privateKey
* @return
*/
public static byte[] decrypt(byte[] encryptedData, String privateKey) {
return decrypt(encryptedData, HexUtil.hexToByte(privateKey));
}
/**
* 解密
* @param encryptedData
* @param privateKey
* @return
*/
public static String decryptToString(byte[] encryptedData, String privateKey) {
return new String(decrypt(encryptedData, HexUtil.hexToByte(privateKey)), StandardCharsets.UTF_8);
}
/**
* 解密
* @param encryptedData
* @param privateKey
* @return
*/
public static String decryptToString(String encryptedData, String privateKey) {
return new String(decrypt(Hex.decode(encryptedData), HexUtil.hexToByte(privateKey)), StandardCharsets.UTF_8);
}
}
此外,CA(Certificate Authority,证书颁发机构)机构通常也使用非对称加密的方式来确保数字证书的安全性。CA机构在数字证书颁发过程中起到了信任的中介角色,其操作基于公钥基础设施(PKI)。
下面是CA机构使用非对称加密的一般流程:
- 证书请求: 实体(通常是个人或组织)向CA机构提交证书请求,请求包括实体的公钥和一些身份信息。
- 验证身份: CA机构对证书请求中的身份信息进行验证,确保请求者确实拥有所声明的身份。这可以通过一系列验证步骤来完成。
- 颁发数字证书: 验证通过后,CA机构使用自己的私钥对实体的公钥和身份信息进行签名,生成数字证书。这个签名过程就是使用非对称加密,其中CA的私钥用于签署证书信息。
- 证书分发: CA机构将生成的数字证书发送给请求者,同时可以将证书公开发布到公共目录中,以便其他人可以验证证书的真实性。
- 证书验证: 在通信过程中,当其他人需要验证实体的身份时,他们可以使用CA机构公开的公钥来验证数字证书的签名。如果验证通过,就可以信任该数字证书所附的公钥。
非对称加密的优势在于它提供了更高的安全性。即使在公共环境下传输公钥,也无法通过公钥轻松计算出私钥。这使得非对称加密在安全地实现身份验证、数字签名和加密通信等场景中发挥重要作用。
相对的,什么是对称加密呢?
对称加密是一种加密算法,它使用相同的密钥同时进行数据的加密和解密。这意味着在对称加密中,使用加密和解密操作的相同密钥。对称加密算法在加密和解密的过程中都使用相同的密钥,因此密钥的保管和分发变得至关重要。
我们了解的国密SM4就是采用的对称加密的方式实现的,源码工具如下:
/**
* (分组密码算法)国密对称加密算法
*/
@Slf4j
public abstract class SM4Util {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final Charset ENCODING = StandardCharsets.UTF_8;
public static final String ALGORITHM_NAME = "SM4";
// 加密算法/分组加密模式/分组填充方式
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
// 128-32位16进制;256-64位16进制
public static final int DEFAULT_KEY_SIZE = 128;
public static final String DEFAULT_KEY = "86C63180C2806ED1F47B859DE501215B";
public static final String DEFAULT_IV = "8F5CB6272B594B53AD1A2197361378DC";
private static Cipher generateCipherECB(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
cipher.init(mode, new SecretKeySpec(key, ALGORITHM_NAME));
return cipher;
}
private static Cipher generateCipherCBC(String algorithmName, int mode, byte[] key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
cipher.init(mode, new SecretKeySpec(key, ALGORITHM_NAME), new IvParameterSpec(iv));
return cipher;
}
public static String generateKey() {
try {
return HexUtil.byteToHex(generateKey(DEFAULT_KEY_SIZE));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}
protected static byte[] encryptECBPadding(byte[] data, byte[] key) throws Exception {
Cipher cipher = generateCipherECB(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
protected static byte[] decryptECBPadding(byte[] encrypted, byte[] key) throws Exception {
Cipher cipher = generateCipherECB(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(encrypted);
}
protected static byte[] encryptCBCPadding(byte[] data, byte[] key, byte[] iv) throws Exception {
Cipher cipher = generateCipherCBC(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv);
return cipher.doFinal(data);
}
protected static byte[] decryptCBCPadding(byte[] encrypted, byte[] key, byte[] iv) throws Exception {
Cipher cipher = generateCipherCBC(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(encrypted);
}
public static String encrypt(String source) {
return encrypt(source, DEFAULT_KEY);
}
public static String encrypt(byte[] source) {
return encrypt(source, DEFAULT_KEY);
}
public static String encrypt(String source, String hexKey) {
return encrypt(source.getBytes(ENCODING), hexKey);
}
public static String encrypt(byte[] source, String hexKey) {
byte[] cipherArray;
try {
cipherArray = encryptECBPadding(source, ByteUtils.fromHexString(hexKey));
return ByteUtils.toHexString(cipherArray);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
public static byte[] decrypt(String encrypted) {
return decrypt(encrypted, DEFAULT_KEY);
}
public static String decryptToString(String encrypted) {
return decryptToString(encrypted, DEFAULT_KEY);
}
public static String decryptToString(String encrypted, String hexKey) {
return new String(decrypt(encrypted, hexKey), ENCODING);
}
public static byte[] decrypt(String encrypted, String hexKey) {
try {
return decryptECBPadding(ByteUtils.fromHexString(encrypted), ByteUtils.fromHexString(hexKey));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return encrypted.getBytes(StandardCharsets.UTF_8);
}
public static String encrypt(String source, String hexKey, String iv) {
return encrypt(source.getBytes(ENCODING), hexKey, iv);
}
public static String encrypt(byte[] source, String hexKey, String iv) {
byte[] cipherArray;
try {
cipherArray = encryptCBCPadding(
source
, ByteUtils.fromHexString(hexKey)
, ByteUtils.fromHexString(iv));
return ByteUtils.toHexString(cipherArray);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new RuntimeException("数据加密失败", e);
}
}
public static String decryptToString(String encrypted, String hexKey, String iv) {
return new String(decrypt(encrypted, hexKey, iv), ENCODING);
}
public static byte[] decrypt(String encrypted, String hexKey, String iv) {
try {
if (StringUtils.isBlank(encrypted)) {
return new byte[] {};
}
return decryptCBCPadding(
ByteUtils.fromHexString(encrypted)
, ByteUtils.fromHexString(hexKey)
, ByteUtils.fromHexString(iv));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return encrypted.getBytes(StandardCharsets.UTF_8);
}
}
相信你也一定听过国密SM3算法,它是对称加密还是非对称加密呢?
其实SM3是一种密码杂凑算法,用于生成消息的哈希值,主要用于数据完整性验证、数字签名等场景,而不是进行加密和解密操作。所以它既不是对称加密也不是非对称加密,它常常与SM2与SM4组合一起使用,可以把它看成一个随机数。
例如:
系统数据存储加密应采用SM3+SM4的实现方式。
系统数据传输加密应采用SM2+SM3的实现方式。
总结
在系统安全测试中,我们通过采用多层加密算法、合理的安全架构设计以及严格的数据安全措施,提高了系统在身份认证、授权、访问控制、审计和资产保护等方面的安全性。这有助于在互联网和新兴技术时代中更好地处理数据安全问题,特别适用于大型政府项目等对安全性要求较高的场景。数据安全相关的知识属于另一个领域了,知识点真是深不可测,还需要平时多学习,积累更多的知识储备才行。