根据《中华人民共和国数据安全法》中第三条,给出了数据安全的定义,是指通过采取必要措施,确保数据处于有效保护和合法利用的状态,以及具备保障持续安全状态的能力。
在互联网盛行的今天,不法分子可以通过网络攻击,网络欺骗等手段窃取用户的个人信息甚至企业的机密信息。而且在拿到部分用户信息后就可以唯一的锁定具体某一个人,因此数据的保密显得格外重要。
为了保证客户数据的安全性, GrowingIO 通过构建一个安全的软件运行时与数据静态存储加密来提高数据生产安全性。下面我们将详细介绍数据安全落地的过程。
软件运行安全也就是企业的系统运行安全,这主要包含两个方面: 数据逻辑隔离:系统通过用户的权限与角色,给出专有的数据操作集合。 KMS秘钥管理:系统所依赖的数据库,中间件不会因为秘钥的泄露而造成数据泄露。
数据逻辑隔离主要是对平台上操作的用户进行身份,角色,权限的认证。 认证流程:
所有平台的用户通过RBAC 权限模型分配不同的角色和权限。只有拥有对应角色权限的用户才能查看或进行对应的操作。
KMS 即(key manage system)秘钥管理系统。目前亚马逊云和阿里云等云产品都有自己的解决方案。KMS 支持多种类型的的数据库,中间件,以及应用秘钥管理等功能。使用KMS 之后所有数据库,中间件等的用户名,密码对产研均不可见,应用静态数据加密对应的秘钥也不可见。 目前支持数据库和中间件以及系统秘钥:
【交互流程】
【示例展示】
以亚马逊云(AWS)为例:
应用配置
kms:
enabled: false
aws:
region: ""
access_key_id: ""
secret_access_key: ""
#应用中添加
spring:
redis:
kms-key: "test/json/redis"
datasource:
kms-key: "test/postgresql/accounts"
运行初始化
示例以postgresql 数据库HikariCP 连接池为例:
@Bean
@ConditionalOnProperty(value = "kms.enabled",havingValue = "true")
public HikariDataSource dataSource(DataSourceProperties properties, KmsProperties kms,Configs configs) {
SecretsManagerClient client = SecretsManagerClientBuilder.build(kms.getRegion(),kms.getAwsAccessKeyId(),kms.getAwsSecretAccessKey());
AwsSecretProvider secretProvider = new AwsSecretProvider(client);
String secret;
try{
secret = secretProvider.getSecret(configs.getDataSourceKmsKey());
//secret是json格式的链接信息
final HashMap map = Jackson.readValue(secret, HashMap.class);
String url = String.format("jdbc:postgresql://%s:%s/%s?useUnicode=true&useSSL=false&characterEncoding=utf8",map.get("host"),map.get("port"),map.get("dbname"));
properties.setUrl(url);
properties.setUsername(map.get("username"));
properties.setPassword(map.get("password"));
}catch (Exception e) {
log.error("kms database error",e);
}
HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
在实际开发中用到的任何数据库,中间件都可以通过KMS 来管理对应的用户名,密码等敏感信息。
【什么是PII】
PII 即个人身份信息 (Personally identifiable information) 是任何可能识别特定个人的数据。任何可用于将一个人与另一个人区分开来并可用于对以前匿名数据进行去匿名化的信息都可以被视为 PII。 PII 可以单独使用或与其他相关数据一起使用来识别个人,并且包含可以唯一识别个人的直接标识符(例如护照信息)或准标识符(例如种族),可以与其他准标识符结合使用标识符,如出生日期,以成功识别个人。
【PII 意义】
保护 PII 对于个人隐私、数据隐私、数据保护、信息隐私和信息安全至关重要。仅凭个人信息的一小部分,窃贼就可以以该人的名义创建虚假账户、产生债务、伪造护照或将个人身份出售给犯罪分子。随着个人的个人数据每天被记录、跟踪和使用——例如在使用指纹的生物识别扫描和用于解锁设备的面部识别系统中——保护个人身份和他们独有的任何识别信息变得越来越重要。
【PII 加解密】
PII 加密: 对于所有入库的用户敏感数据使用加密算法进行加密。 PII 解密:普通用户只能查看加密的密文数据,对于有业务需要的通过授权可以查看明文数据。
GrowingIO 默认选择AES(AES/CBC/PKCS5Padding) 算法 ,采用256长度的秘钥来作为PII 数据加密的实现。
执行流程
数据加密
数据解密
示例展示
PII 可以从配置中心或KMS 中来获取加解密需要的秘钥数据。
秘钥获取:
// KMS 方式
SecretsManagerClient client = SecretsManagerClientBuilder.build(kms.getRegion(),kms.getAwsAccessKeyId(),kms.getAwsSecretAccessKey());
AwsSecretProvider secretProvider = new AwsSecretProvider(client);
String secret = secretProvider.getSecret("test/pii/json");
//secret是json格式的链接信息
final HashMap map = Jackson.readValue(secret, HashMap.class);
String algorithmIv = map.getOrElse("algorithm_iv", "");
// 向量
String iv = DatatypeConverter.parseHexBinary(algorithmIv);
// 秘钥
String encryptionKey = DatatypeConverter.parseHexBinary(map("encryption_key"));
String decryptionKey = DatatypeConverter.parseHexBinary(map("decryption_key"));
// 配置中心方式
// 向量
String iv = Hex.decodeHex(Configs.Encry.configCenterIv);
// 秘钥
String secret = Hex.decodeHex(Configs.Encry.configCenterSecret);
执行加密或者解密操作:
通过PII 进行数据处理后,数据库中敏感数据全为加密存储。页面渲染依据用户权限判断是否加密或者解密展示。
数据存储与渲染
数据库数据:
只有管理员登录后渲染解密后的数据:
一、向量IV 的支持
在AES 加密算法中:AES_ECB_PKCS5Padding 不支持向量,AES_CBC_PKCS5Padding支持向量且安全性更高。如果原来已经有用到AES 算法要考虑兼容性。
二、Base 64 编码带来的问题
现象:如果加密的字符串比较长,加密后的密文将长度超过76时,密文中会被加入一个换行符,导致后续按行解析异常。
在JDK1.8 之后Base64 工具类移到了java.util 包中,为了向低版本的JDK 兼容,Base64 中 MimeEncode,采用的方式:
private static final int MIMELINEMAX = 76;
private static final byte[] CRLF = new byte[] {'\r', '\n'};
static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
也就是说在加密串的长度超过76时会加上 '\r' 或者 '\n',这就导致如果按行进行数据的某些操作将会发生致命错误。所以推荐使用Base64中Encode的实现:
static final Encoder RFC4648 = new Encoder(false, null, -1, true);
static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
这样不管加密后的字符串长度有多长都不会发生换行的问题。
参考JDK 源码和秘钥标准:
RFC 4648 http://www.ietf.org/rfc/rfc4648.txt
RFC 2045 http://www.ietf.org/rfc/rfc2045.txt