bk-user
bk-user copied to clipboard
【暂不支持】数据库存储字段加密处理;页面展示信息脱敏处理
需求背景 1、按照《个人信息安全法》实现用户数据的保护,数据库中,涉及到用户个人信息的数据需要进行加密储存,如姓名(第三级)、手机(第二级)、邮箱(第二级)、密码(第四级)。 2、第一条中信息中,需要实现加密传输。主要场景为用户登陆时需要密文传输。 3、加密算法:通过国密(SM4)、AES-GCM(>=128bits) (SM4算法是某客户强制要求的,AES也有客户提出,但需求强度不大,麻烦 @Xmandon 评估一下是否可以同步支持
原始需求截图:
需求描述 1、不同客户可能会要求页面展示信息脱敏,希望在用户字段配置时,可以配置用户字段信息在页面上是否脱敏展示,同时配置字段信息在数据库是否加密,以及加密方式 2、用户登录时,用户密码密文传输 3、(拓展)《个人信息安全法》中提到,信息传输需要符合相关的保密法规,一般客户都会较为关注内网安全,因此在内网中用户管理数据被上其他saas调用时,可以为明文传输。但用户管理通过开放接口向外部系统推送数据时,可能需要支持加密传输
优先设计和排期
2、用户登录时,用户密码密文传输
done
https://cloud.tencent.com/document/product/573/49386 https://cloud.tencent.com/developer/article/1695498
国密加密的事情,暂时不支持,需要统一调研方案实现。
1.优先支持 <国密加密-手机、邮箱、ip字段>; 2.本次需求开发暂不包括加密条件下支持搜索功能 3.字段脱敏展示,转另一个issue跟进:https://github.com/TencentBlueKing/bk-user/issues/769
开发方案:
加密字段
Profile: display_name / telephone / email
LogIn: extra_value (包含了client_ip)【整个extra_value进行加密】
加密思路
- 支持对应字段存储加密 (Django Encrypt Field)【上层接口/产品无影响, 在ORM 查询就消化掉了加解密】
- 可注入加密扩展
- 对存量数据进行加密适配【数据升级脚本】
加密方案
- 支持对应字段存储加密
encryption_algorithm = settings.EncryptionAlgorithm
encryption_algorithm_map = {
"SM4": SM4Handler
}
class EncryptedMixin(models.Field):
internal_type = "CharField"
prepared_max_length = None
def __init__(self, key=None, **kwargs):
kwargs.setdefault('max_length', self.prepared_max_length)
# 设置成⼀个注⼊, 根据环境变量确定这套环境的加密⽅式是什么
self.handler = encryption_algorithm_map["encryption_algorithm"]
super().__init__(**kwargs)
def get_db_prep_value(self, value, connection, prepared=False):
value = super().get_db_prep_value(value, connection, prepared)
if value is not None:
encrypted_text = self.handler.encrypt(value)
if self.max_length and len(encrypted_text) > self.max_length:
raise EncryptedFieldException(f"Field {self.name} max_length={self.max_length}encrypted_len={len(encrypted_text)}")
return encrypted_text
return None
def from_db_value(self, value, expression, connection, *args):
if value is None:
return value
return self.to_python(self.handler.decrypt(value))
def get_internal_type(self):
return self.internal_type
class EncryptedTextField(EncryptedMixin, models.TextField):
internal_type = "TextField"
class EncryptedCharField(EncryptedMixin, models.CharField):
internal_type = "CharField"
- SM4加密处理
from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT
SM4_KEY = settings.SM4_KEY # bytes类型
class SM4Handler():
def encrypt(self, plain_text) -> str:
# 将python格式数据转换成json,再转换成bytes,sm4加密bytes格式数据;
value = bytes(json.dumps(plain_text), 'utf-8')
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(SM4_KEY, SM4_ENCRYPT)
# 使用crypt_ecb进行加密value
encrypt_value = crypt_sm4.crypt_ecb(value)
return encrypt_value
def decrypt(self, encrypted_text) -> str:
crypt_sm4 = CryptSM4()
crypt_sm4.set_key(SM4_KEY, SM4_DECRYPT)
# 将加密数据通过SM_KEY进行解密;
decrypt_value = crypt_sm4.crypt_ecb(encrypted_text)
# 解密数据转换为python格式数据;
value = json.loads(str(decrypt_value, 'utf-8'))
return value
- model处理
class User(models.Model):
display_name = EncryptedCharField(algorithm="sm4", verbose_name=_("用户名"), default=None)
telephone = EncryptedCharField(verbose_name=_("手机号"), default=None)
email = EncryptedCharField(verbose_name=_("邮箱地址"), default=None)
extras = EncryptedJsonField(verbose_name=_("自定义字段"), default={})
- SM4Handler to SM4CryptHander, 并且继承一个基类
- 需要支持 环境变量-> 配置文件 配置加密算法/加密key
- 需要确认精确查询是否能拿到
object.get(telephone=123456)
/object.filter(email="[email protected]")
- 加密后的字段长度, 会不会超过原先的设置(需要考虑, 最大长度会是多少, 新装及环境升级)
- 写一个测试demo验证下, 然后重新贴下代码(上面的代码格式是错乱的)
加密后, 前端的功能以及后台接口会受到影响
例如手机号加密后, 无法模糊搜索, 产品前端的搜索入口+后台搜索接口都会受影响, 只能精确匹配 => 影响用户体验及上游系统的用户体验