1

你好 Stackoverflow 社区,

我在电子签名方面有困难,尤其是在技术要求方面如何适用新加坡法律。

根据法律,新加坡接受电子签名,如果签名是:

  • 对使用它的人来说是独一无二的;
  • 能够识别该人;
  • 以使用它的人单独控制的方式或使用手段创建
    ;和
  • 以某种方式链接到与其相关的电子记录,如果记录被更改,电子签名将失效。

我们可以通过 HTML5 画布制作电子签名,然后将其保存为数据库字符串。这意味着第 1 点、第 2 点和第 3 点都可以。

第 4 点出现问题,我们确实可以将签名和数据组合在一个加密字符串中,但是,由于我们拥有签名和数据,我们将能够编辑数据并创建一个新字符串:

|id|name|price|signature|final_hash|
------------------------------------
|12|test|40000|data:base|3edcde4642|

所以我们想到了另一种方法,就是在 final_hash 中混入一些我们不知道的参数,比如签名者的 IP 地址,他/她的用户代理……但是,由于我们不保存这些值,这意味着我们将无法稍后检查是否修改了某些值。

这就是我们被卡住的地方,你知道如何做到这一点吗?对我来说,既能够检查是否没有数据被修改,又不能满足以后不能修改数据似乎是不可能的,但我当然可能错了。

非常感谢你的帮助!

4

1 回答 1

7

注意:我使用术语“文档”来指代您要签署的任何内容。


一些误解

  • 首先,使用 HTML5 画布或类似方法得到的不是签名,而是指纹。我的意思是它可以识别客户,但它不能签署文件(至少不能单独签署)。两个警告:

    1. 指纹是浏览器的,而不是用户的。
    2. 会有碰撞。事实上,为了隐私,有一种推动指纹浏览器更难的推动力,主要是因为公司可以在没有 cookie 的情况下使用指纹来跟踪你。
  • 您不应该通过将指纹连接到数据来进行签名。那不是签名,那是salt,也不是处理 salt 的最佳方法。

顺便说一句,ClientJS是创建浏览器指纹的好工具


您的要求

  1. 对使用它的人来说是独一无二的;
  2. 能够识别该人;

这些要求拼写为“识别”。这意味着您需要该人的ID。

  1. 以使用它的人单独控制的方式或使用手段创建;和

此要求拼写“身份验证”。这意味着您需要验证人们是否是他们声称的人。您可能已经看到了这个理论,您可以通过以下方式做到这一点:a) 某人的身份即生物特征) b) 某人知道的某事(即密码)或 c) 该人拥有的某事(即访问电子邮件帐户、手机, ETC...)。

令我困扰的是,您似乎正在使用浏览器指纹来进行身份验证和身份验证。这可能会使伪造请求变得太容易,从而看起来像是另一个用户。

通常在这种情况下你会做的是使用用户名和密码进行传统的身份验证过程,然后发出一个 cookie。你为什么要避免这种情况?

  1. 以某种方式链接到与其相关的电子记录,如果记录被更改,电子签名将失效。

这个要求拼写“签名”。这可能意味着电子记录将是公开的,我们希望能够防止伪造。

既能够检查是否没有数据被修改,又不能在以后修改数据,这似乎是不可能的

一般的想法是让签名在文档被修改时失效。如果要使修改正式,则需要创建一个新签名。

为了防止第三方从修改后的消息中创建签名,您需要添加一些密钥。现在,这提出了两个问题:

  1. 第三方应该能够验证签名吗?如果他们应该这样做,您需要一个不对称算法。

  2. 是否应该保护文档免受您(或有权访问服务器的人)的侵害?如果它们应该受到保护,则表明您需要为每个用户设置一个秘密。最简单的解决方案是设置密码。


不对称解决方案

要在 PHP 中实现非对称解决方案,我建议使用phpseclib

按照他们的示例创建密钥对

include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
$pair = $rsa->createKey();
$privatekey = $pair['privatekey'];
$publickey = $pair['publickey'];

该代码将使用从服务器收集的熵来生成密钥对。

然后就可以用它来做简单的RSA 签名和验证了。

include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
$rsa->loadKey('...'); // private key
$plaintext = '...';
$signature = $rsa->sign($plaintext);

$rsa->loadKey('...'); // public key
echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified';

如果需要在客户端验证签名,可以使用jsrsasign

对于您的用例,您只需要一个用于服务器的密钥对。这里的概念是服务器正在签署文档。如果签名匹配,则说明指纹是正确的。

无论哪种方式,您都应该保密您的私钥(永远不要将其发送给客户端),并且您可以与任何您需要能够验证签名的人共享公钥。

使用它,服务器可以签署识别用户/客户端的文档(例如包括指纹)并发布文档和签名。该文档将是完全可读的(它未加密),但如果有人修改它,签名将不再有效。

为了验证签名,他们需要公钥。但是,公钥对于创建假签名没有用处,为此他们需要获取私钥。


对称解

对称算法的主要优势在于性能。对称算法的缺点是您需要密钥来验证它。

因此,如果您需要第三方能够验证签名(或者即使您需要在客户端进行验证),您也会暴露用于创建签名的密钥。

在这种情况下,签名更容易实现。使用密钥签名使用hash_hmac。要验证签名,请重复该过程并将其与您获得的签名进行比较。


每个用户都有一把钥匙

我不确定您是否试图保护自己的数据。也就是说,如果您的目标是防止您(或您的团队/或管理服务器的任何人)篡改数据。

如果这是您想要的,您可以使用密码保护您的密钥。对于非对称解决方案,phpseclib 允许您设置用于加密私钥的密码(因此需要能够签名)。对于对称解决方案,您可以直接使用密码作为密钥。

如果您想要从您的指纹生成该密码,您可以使用密钥派生函数来实现……但是,请记住,会有指纹冲突,并且拥有唯一的浏览器指纹可以被视为隐私问题。


密钥导出函数

您可以使用密钥派生函数从外部输入(浏览器指纹、用户密码等)获取加密密钥。

从最差到最好的选择:

  • 截断以调整指纹大小。永远不要这样做。这浪费了指纹的熵。它使碰撞很可能发生。
  • 哈希指纹。两者都不要。任何可以获取指纹并知道您使用什么哈希的人都可以获得密钥。
  • 将指纹与胡椒(每个人都使用相同的盐)连接起来进行哈希处理。还是不好
  • 散列与盐连接的指纹(每个人都是唯一的)。许多人说这是第一个理智的选择。然而,你应该记住使用一个好的散列算法。
  • 使用基于散列的消息验证码hash_hmac. 对我来说,这是第一个理智的选择。将连接的散列更改为对hash_hmac. 此外,它将保护您免受散列算法的部分原像漏洞(已知或待发现)。
  • 使用专用的密钥派生算法。在标准的 PHP 函数中,我建议使用hash_hkdfor hash_pbkdf2

注意:有时人们(包括过去的我)将这些密钥派生函数称为哈希函数。原因是当你使用它们时,你不会(直接)调用你的常规哈希函数,它们给你的是技术上的哈希。然而,实际上,它们不是散列算法。它们是建立在散列之上的算法……事实上,使用它们的参数之一是在内部使用什么散列函数。

于 2017-04-15T09:17:22.377 回答