如何以符合 PCI 的方式在 iOS / Android 上存储信用卡信息
How to store Credit Card info on iOS / Android in a PCI-compliant manner
我正在构建一个接受付款的移动应用程序。用户输入他们的 CC 详细信息,付款信息通过 HTTPS 提交到零售商的 POS 系统。 POS 直接处理付款并需要实际的信用卡信息才能工作,因此我们不能使用像 Stripe 这样的服务来存储卡,并给我们返回一个令牌来处理付款。
由于应用程序的性质,用户会定期付款,因此我想存储他们的 CC 信息以方便使用。然而,这不是经常性计费,用户将随意发起交易。因此,我不需要将 CC 集中保存在服务器上,我正在考虑使用这种方法在每个用户的设备上存储单独的卡片:
- 收集 CC 号码和有效期
- 使用 AES256 加密,使用 CVC 作为密钥(未存储 CVC)
- 然后将加密数据存储在 iOS 钥匙串中(或 Android 的等效项)
- 要进行支付,(a) 从钥匙串中取出数据,并且
- (b) 用户必须输入CVC才能解密CC信息
这个想法是,如果用户知道 CVC,他们无论如何都可能拥有该卡,因此他们不需要尝试破解设备。
对于加密,我正在考虑使用 RNCryptor lib。其主要功能之一是将常用密码自动转换为两个 256 位密钥的 'random' 字节序列,用于加密和身份验证。密钥拉伸是通过 10k 轮 PBKDF2 实现的。实现细节在link,简而言之:
- AES-256 加密
- CBC 模式
- 使用 PBKDF2 扩展密码
- 密码加盐
- 随机IV
- 加密然后散列 HMAC
问题:
我对数学的理解不够好,无法判断仅使用 3 个 CVC 数字播种 RNCryptor 的密钥扩展实现是否会产生统计上足够随机的密钥。我无法找到任何关于 RNCryptor 保持安全所需的密码规范的文档。对此有任何想法将不胜感激。使用这个库就这么简单:
// Encryption
NSData *data = ...
NSString *password = @"Secret password";
NSData *ciphertext = [RNCryptor encryptData:data password:password];
// Decryption
NSError *error = nil;
NSData *plaintext = [RNCryptor decryptData:ciphertext password:password error:&error];
if (error != nil) {
NSLog(@"ERROR:%@", error);
return
}
// ...
在 AES256 加密 CC 信息 iOS 钥匙链(或机器人等效物)上的故事时,是否有重要没有启用设备锁定密码?我的想法是,信息已经被 AES256 加密,它可以存储在没有钥匙串加密的设备上吗?
考虑到没有中央服务器存储大量 CC 编号,PCI 合规性的哪些部分与这种情况相关?我尝试阅读 PCI 规范,但文档是一个难以导航的迷宫:(
正如 Ebbe 指出的那样,仅使用 CVC 作为密钥,即使使用 PBKDF2 从 CVC 派生也是不安全的,只有 1000 个可能的密钥。密钥中必须包含其他内容。
日期作为部分婴儿床,因为它具有已知的格式和有限的值。还要注意不要包括任何其他婴儿床,例如字段分隔符或字段指示符。
信用卡账号验证码也是个坑
为了保证钥匙串的安全,用户必须输入设备锁密码。
必须避免越狱和获得 Root 权限的设备,这很难确定。
只要只保存 CC# 和到期日期,而不保存任何轨道 2 数据,并且它是按照 PCI 标准加密的,你应该没问题。
查看 PCI Point to Point Encryption Standard,它在 PCI 站点上是免费的。参见 Table 2,应用程序开发人员。
最后请 PCI 审核员审核您的方案,这只是 "best information" 提供的信息,没有经过全面评估。
Update based on the OP's comment:
有 1000 个 CVC 可以尝试。 RNCryptor 每次 CVC 尝试需要大约 200 毫秒,这意味着所有 1000 次都可以在大约 4 分钟内尝试——哎哟。 RNCryptor 具有身份验证功能,因此在尝试正确的 CVC 时会立即获知。这一点也不安全,而且身份验证对您不利。
没有身份验证就需要依赖婴儿床。第一个婴儿床是校验位,它将排除约 900 个 CVC,剩下 100 个。
但是根据加密的格式,情况确实更糟。使用不正确的密钥解密将 return 本质上是随机字节。如果 CC# 和日期是一个字符串,结果是一个数字字符串的几率实际上是压缩的,所以再次正确的解密将立即被知道。最好的办法是将 CC# 转换为一个大整数,将日期转换为数字日+年并对其进行加密。但即便如此,也有校验位和用于验证解密的有效日期。在加密中不包括到期日期实际上更安全。
最终使用 CVC 作为密钥是不安全的,需要更长的密钥。
Keychain 不保护其内容不受设备所有者的影响,实际上设备所有者是拥有访问权限的人,密码是决定访问权限的因素,因此也是设备所有者。
如果您不想自己实现所有内容,可以查看 iOS 和 Android 的 VGSCollect SDK。 SDK 收集 PCI 范围内的所有数据。所有加密人员都在他们身边完成,因此您只需获得别名,您可以在发送付款请求时使用该别名。
我正在构建一个接受付款的移动应用程序。用户输入他们的 CC 详细信息,付款信息通过 HTTPS 提交到零售商的 POS 系统。 POS 直接处理付款并需要实际的信用卡信息才能工作,因此我们不能使用像 Stripe 这样的服务来存储卡,并给我们返回一个令牌来处理付款。
由于应用程序的性质,用户会定期付款,因此我想存储他们的 CC 信息以方便使用。然而,这不是经常性计费,用户将随意发起交易。因此,我不需要将 CC 集中保存在服务器上,我正在考虑使用这种方法在每个用户的设备上存储单独的卡片:
- 收集 CC 号码和有效期
- 使用 AES256 加密,使用 CVC 作为密钥(未存储 CVC)
- 然后将加密数据存储在 iOS 钥匙串中(或 Android 的等效项)
- 要进行支付,(a) 从钥匙串中取出数据,并且
- (b) 用户必须输入CVC才能解密CC信息
这个想法是,如果用户知道 CVC,他们无论如何都可能拥有该卡,因此他们不需要尝试破解设备。
对于加密,我正在考虑使用 RNCryptor lib。其主要功能之一是将常用密码自动转换为两个 256 位密钥的 'random' 字节序列,用于加密和身份验证。密钥拉伸是通过 10k 轮 PBKDF2 实现的。实现细节在link,简而言之:
- AES-256 加密
- CBC 模式
- 使用 PBKDF2 扩展密码
- 密码加盐
- 随机IV
- 加密然后散列 HMAC
问题:
我对数学的理解不够好,无法判断仅使用 3 个 CVC 数字播种 RNCryptor 的密钥扩展实现是否会产生统计上足够随机的密钥。我无法找到任何关于 RNCryptor 保持安全所需的密码规范的文档。对此有任何想法将不胜感激。使用这个库就这么简单:
// Encryption
NSData *data = ...
NSString *password = @"Secret password";
NSData *ciphertext = [RNCryptor encryptData:data password:password];
// Decryption
NSError *error = nil;
NSData *plaintext = [RNCryptor decryptData:ciphertext password:password error:&error];
if (error != nil) {
NSLog(@"ERROR:%@", error);
return
}
// ...
在 AES256 加密 CC 信息 iOS 钥匙链(或机器人等效物)上的故事时,是否有重要没有启用设备锁定密码?我的想法是,信息已经被 AES256 加密,它可以存储在没有钥匙串加密的设备上吗?
考虑到没有中央服务器存储大量 CC 编号,PCI 合规性的哪些部分与这种情况相关?我尝试阅读 PCI 规范,但文档是一个难以导航的迷宫:(
正如 Ebbe 指出的那样,仅使用 CVC 作为密钥,即使使用 PBKDF2 从 CVC 派生也是不安全的,只有 1000 个可能的密钥。密钥中必须包含其他内容。
日期作为部分婴儿床,因为它具有已知的格式和有限的值。还要注意不要包括任何其他婴儿床,例如字段分隔符或字段指示符。
信用卡账号验证码也是个坑
为了保证钥匙串的安全,用户必须输入设备锁密码。
必须避免越狱和获得 Root 权限的设备,这很难确定。
只要只保存 CC# 和到期日期,而不保存任何轨道 2 数据,并且它是按照 PCI 标准加密的,你应该没问题。
查看 PCI Point to Point Encryption Standard,它在 PCI 站点上是免费的。参见 Table 2,应用程序开发人员。
最后请 PCI 审核员审核您的方案,这只是 "best information" 提供的信息,没有经过全面评估。
Update based on the OP's comment:
有 1000 个 CVC 可以尝试。 RNCryptor 每次 CVC 尝试需要大约 200 毫秒,这意味着所有 1000 次都可以在大约 4 分钟内尝试——哎哟。 RNCryptor 具有身份验证功能,因此在尝试正确的 CVC 时会立即获知。这一点也不安全,而且身份验证对您不利。
没有身份验证就需要依赖婴儿床。第一个婴儿床是校验位,它将排除约 900 个 CVC,剩下 100 个。
但是根据加密的格式,情况确实更糟。使用不正确的密钥解密将 return 本质上是随机字节。如果 CC# 和日期是一个字符串,结果是一个数字字符串的几率实际上是压缩的,所以再次正确的解密将立即被知道。最好的办法是将 CC# 转换为一个大整数,将日期转换为数字日+年并对其进行加密。但即便如此,也有校验位和用于验证解密的有效日期。在加密中不包括到期日期实际上更安全。
最终使用 CVC 作为密钥是不安全的,需要更长的密钥。
Keychain 不保护其内容不受设备所有者的影响,实际上设备所有者是拥有访问权限的人,密码是决定访问权限的因素,因此也是设备所有者。
如果您不想自己实现所有内容,可以查看 iOS 和 Android 的 VGSCollect SDK。 SDK 收集 PCI 范围内的所有数据。所有加密人员都在他们身边完成,因此您只需获得别名,您可以在发送付款请求时使用该别名。