京东开放平台敏感信息加密对接典型方案

-

先来个总结,再贴京东开放平台官网原文。

如果经历过淘宝敏感信息加密的小伙伴就基本不用看这个了,京东当前的加密方案,就是淘宝开放平台之前的加密方案,目前淘宝最新的加密方案是oaid方案,京东没有跟进,京东是直接Aes加解密的。


以下是京东开放平台关于加解密说明的地址 https://jos.jd.com/commondoc?listId=345

原本如下,也可自行访问官方地址自行查看。


4.1 Number字段类型加密方案
4.1.1 背景
目前明文通过AES加密之后变成一串Base64的字符串,这种情况下SDK转换会报错,而且可能跟db存储的类型不匹配。
例如:jingdong.pop.loc.broadband.order.findPage接口的phone_number加密字段是Number类型。
问题:
a ) 老版本SDK
public class PopLocBroadbandOrderFindPageResponse extends AbstractResponse{
private PageResult findpageResult;
}
public class PageResult implements Serializable{
其他字段。。
private java.util.List<BroadBandOrderInfo> data;
其他字段…
}
public class BroadBandOrderInfo implements Serializable {
其他字段。。
private java.lang.Long phoneNumber;
其他字段…
}
b ) 加密后的信息格式AATGzmuhp0shxEHC2L6ZT2TbFRBAsDJlQtZR3YrbzFAlacgLPBc=
c ) 从“AATGzmuhp0shxEHC2L6ZT2TbFRBAsDJlQtZR3YrbzFAlacgLPBc=” 转换到phoneNumber失败。
4.1.2 解决方案
加密逻辑变更:
阶段1(Token白名单):加密请求,在返回密文的同时也返回明文,用于开发者做切换。
api返回加密结果:{"phone_number"=12345678910, "encrypt_phone_number"=加密(12345678910)} // 新增一个返回字段 encrypt_phone_number
阶段2(全店铺加密):加密请求,不返回Number类型明文字段(相当于明文字段作废)。
api返回加密结果:{“phone_number”=null, "encrypt_phone_number "=加密(12345678910)}
开发者需要做以下几件事情:
a) 下载最新版本SDK
public class BroadBandOrderInfo implements Serializable {
private java.lang.Long phoneNumber;
private java.lang.String encryptPhoneNumber;
其他字段…
}
b) 开发者判断下对应的DB字段(phone_number)是否数字类型, 如果是数字类型:
需要新增string类型字段存储,老数据迁移到新字段。 如果DB已是string类型的则忽略。
c) 取encrypt_phone_number字段做为 phone_number 存储。
4.2 密文字段检索方案
4.2.1 背景
应用在查询页面里,有针对敏感字段进行查询的功能,而这些敏感字段数据加密后不能直接进行匹配检索的,因为每次加密后的数据值是不一样的。所以提供以下相关的方案供开发者进行处理。
4.2.2“精确查询”场景的解决方案
TDEClient.calculateStringIndex("data".getBytes("utf-8"));
如果您需要对一个字段进行精确的检索,那么需要创建一个新字段(索引列)来存储索引值,索引值采用SHA-256进行加密。
a、基于安全设计相同的明文密文不同,所以需要建立一列不可逆并且与明文一一对应的值用作索引,可以调用接口calculateStringIndex,使用明文精确查询时先经过calculateStringIndex运算得到索引值,再从新增的索引列匹配到要查询的行。
b、计算索引列使用信息摘要算法,不可逆,并且要使用明文数据计算索引并存入库。
4.2.3“模糊查询”场景的解决方案
支持两种类型的关键字搜索场景:
a)一种类型为固定格式的关键字搜索,比如手机号码:4127204557
1.可搜索412
2.搜索***720 (***为任意三个ascii字符串)
3.搜索**272 (**为任意两个ascii字符串)
4.*代表一个wildcard ascii字符,但仅能在prefix(字串)

b)搜索任意子字符串,针对住址特別有用,举例来说,针对明文:北京朝阳区北辰西路8号院1号楼
1.搜索北京
2.搜索朝阳
3.搜索北辰西路
4.搜索1号楼
解决方案1:
WildCard Prefix关键字搜索 (搜索功能较固定,但安全性较强)
1.public String obtainWildCardKeyWordIndex(String spt) throws NoValidKeyException, IndexCalculateException
spt: 明文字符串,默認編碼為UTF-8,但字符必須為ascii字符,適用於手機號與身分證號碼(格式長度已固定)
回傳hex的wildcard索引字串 (明文長度兩倍)
2.public String calculateWildCardKeyWord(String queryW) throws NoValidKeyException, IndexCalculateException
queryW: 搜索字符串,默認編碼為UTF-8
回傳hex的keyword wildcard索引字串 (明文長度兩倍)
解决方案2:
任意子字符串关键字搜索(功能较强,但安全性较弱)
a.public String obtainKeyWordIndex(String spt) throws NoValidKeyException, IndexCalculateException
spt: 明文字符串,默认编码为UTF-8,适用于UTF-8字符,适用于姓名与地址搜索(长度未知格式)
回传hex的wildcard索引字串 (约明文长度两倍)
b.public String calculateKeyWord(String queryW) throws NoValidKeyException, IndexCalculateException
代码演示:
TDEClient c1 = null;
try {
c1=SecretJdClient.getInstance("http://api.jd.com/routerjson",accessToken, appKey, appSecret);
String plaintext = "4127204557";
String indexW = c1.obtainWildCardKeyWordIndex(plaintext);
// indexW为97362FB245E58C62DDC4
String subplaintext = "412";
String queryW = c1.calculateWildCardKeyWord(subplaintext);
// queryW 为97362F
// (可直接在SQL query时采用%queryW%方式查询)
// queryW为indexW的子字串
Assert.assertTrue(indexW.contains(queryW));
subplaintext = "413";
queryW = c1.calculateWildCardKeyWord(subplaintext);
// queryW 为97362E
Assert.assertFalse(indexW.contains(queryW));
subplaintext = "***720";
queryW = c1.calculateWildCardKeyWord(subplaintext);
// queryW 为B245E5
Assert.assertTrue(indexW.contains(queryW));
subplaintext = "**720";
queryW = c1.calculateWildCardKeyWord(subplaintext);
// queryW 为2AB747
Assert.assertFalse(indexW.contains(queryW));
subplaintext = "*127";
queryW = c1.calculateWildCardKeyWord(subplaintext);
// queryW 为362FB2
Assert.assertTrue(indexW.contains(queryW));
subplaintext = "******4557";
queryW = c1.calculateWildCardKeyWord(subplaintext);
// queryW 为8C62DDC4
Assert.assertTrue(indexW.contains(queryW));
} catch (Exception e) {
e.printStackTrace();
fail("Should not throw exceptions here.");
}
try {
c1=SecretJdClient.getInstance("http://api.jd.com/routerjson",accessToken, appKey, appSecret);
String plaintext = "尔克孜自治州127號2F";
String indexW = c1.obtainKeyWordIndex(plaintext);
// indexW = RreJFARoKWFARqqBFAS4C31wRbWmAQRrCDFAkkqZFAkYOMjQlOxjfQS56C1wkYOMjQ5QLDC
String subplaintext = "自治州";
String queryW = c1.calculateKeyWord(subplaintext);
// queryW = S4C31wRbWmAQRrCDFA
// (可直接在SQL query时采用%queryW%方式查询)
// queryW为indexW的子字串
Assert.assertTrue(indexW.contains(queryW));
subplaintext = "尔克孜自治州";
queryW = c1.calculateKeyWord(subplaintext);
// queryW = RreJFARoKWFARqqBFAS4C31wRbWmAQRrCDFA
Assert.assertTrue(indexW.contains(queryW));
subplaintext = "克克";
queryW = c1.calculateKeyWord(subplaintext);
// queryW = RoKWFARoKWFA
Assert.assertFalse(indexW.contains(queryW));
subplaintext = "2F";
queryW = c1.calculateKeyWord(subplaintext);
// queryW = kYOMjQ5QLDCg
Assert.assertTrue(indexW.contains(queryW));
subplaintext = "125號";
queryW = c1.calculateKeyWord(subplaintext);
// queryW = kkqZFAkYOMjQltQlbgS56C1w
Assert.assertFalse(indexW.contains(queryW));
} catch (Exception e) {
e.printStackTrace();
fail("Should not throw exceptions here.");
}
使用场景说明:
a.obtainWildCardKeyWordIndex/calculateWildCardKeyWord
建议使用在已知固定format,例如说手机号码与身份证号,然后透过wildcard来进行搜索,像是***123。编码为hex string,长度为明文两倍。
b.obtainKeyWordIndex/calculateKeyWord
建议使用在住址/姓名等无法预期先知道的格式。长度约为明文两倍(中文字符为两倍,ASCII, alphanumeric变成4倍,根据组成会有所变化)。
4.3 历史数据迁移方案
4.3.1 背景
在线上加密覆盖到全量用户后,接下去的一个重要步骤就是要把历史数据全量加密。
4.3.2 解决方案
准备工作:
a.请确认代码逻辑已经做好明文和密文的兼容。
b.确认加密开关已打开。
c.确认从云数据库和JOS API新流入的数据已经是密文的。
数据迁移建议方案:
在确认容错逻辑已完成后进行迁移。先进行少量客户迁移测试之后,可以进行全量迁移测试。
a)首先单个客户数据迁移测试
借用迁移1个客户的机会测试迁移方案。建议迁移量<100万。可以先从较低的并发数开始。例如:并发5-10个线程。
提前记录平时的性能;记录迁移期间再记录迁移时间、迁移过程中的云数据库的IOPS/连接/CPU/TPS/QPS等,和平时的性能做比较。
b)迁移方案
根据前期计算好的迁移速度,计算迁移时间和方案。
首先确定迁移的最小粒度:例如,独立部署的ISV,则可决定一个部署为一个最小粒度。兼顾功能的独立性,以及迁移的总时长(控制在3个小时以内)。
第一天进行迁移时,继续记录数据量、时间……等。
如果迁移过程顺利无问题、且性能负载可以承受,可以在之后逐渐增加线程数。之后,可在第一天的基础上逐步增加线程数和迁移并发数。注意持续监测数据库性能。直至迁移完成。
c)代码逻辑注意
• 仅当用户token有效的,并且用户状态没有过期才能拉取到密钥。在迁移过程中,请排除已经过期token和无效的用户(即:冻结、清退等)。否则造成大量失败的密钥请求,且加密失败。
• 从数据库中拉取用户数据并加密的时候,请注意控制分页大小。

4.4 自有帐号体系接入方案
在ISV接入数据加解密的过程中,在提交应用信息后,需要选择适合自己应用的接入方案。由于我们的加密方案会为不同京东用户的数据生成不同的密钥,不同用户之间的数据是用不同的密钥加密的,应用需要使用用户的授权才能成功加解密。
1) 如果应用本身是基于京东系账号的,那么接入我们的方案就很简单直接了,可以直接使用我们的通用接入方案。
2) 如果应用自己有一套账号体系,而且和京东系账号之间不是一一对应的关系,甚至自有账号有私有独立部署的,那么可能就不适合通用的、简单方案,需要更复杂的自有账号方案。
具体对应的接入方案,请点击查看http://open.jd.com/home/home#/doc/common?listId=956

4.5 自研SDK接入方案
平台上的开发者使用的开发语言各种各样,为了提高开发者接入数据加解密服务的开发效率,平台会为开发者已提供以下3个开发语言SDK(Java/.NET/Php),使用其它开发语言的开发者请使用此手册自行实现加解密,具体代码实现细节可参考已支持的Java版SDK代码
具体对应的接入方案,请点击查看http://open.jd.com/home/home#/doc/common?listId=987

4.6 密文推送到云鼎云数据库的接入方案
宙斯和云鼎已提供的“数据同步服务”(具体可点击查看说明http://open.jd.com/home/home#/doc/common?listId=970)。平台要求敏感字段数据需要密文存储,因此直接推送到云数据库表里的敏感字段数据也需要加密成密文。接入时可使用数据加解密服务的SDK进行加密或解密。具体接入说明请点击查看https://yd-datapush.jdcloud.com
注意事项:
1、在宙斯上新增上线应用会默认已经开启全店铺加密,推送到表里的敏感字段默认是密文;如果需要取消全店铺加密请提交工单处理;
2、如果已配置店铺加密白名单或全店铺加密,接口中的用户帐号字段会同时返回pin和openid,优先使用openid,后续不会再返回pin,具体时间点请关注后续宙斯的公告


本文转载 " 整理 "

原文地址 " "