我需要什么类型的加密?

What type of encryption do I need?

好的,最初的任务是在 2 "friendly" 个能够共享用户 cookie 的网站中跟踪用户(比方说,我有 example.com,我的朋友有 mysite.com 而且他还有一个简单的域。example.com 所以他可以在 .example.com).

上设置 cookie

为了跟踪用户 activity 我们要设置唯一的 cookie,这个 cookie 应该是唯一的并且 32 字节长 (ascii)。从这个角度来看非常简单,可以这样实现:

md5(microtime)

就是这样,但现在我们有了新的 约束:

  1. 我们应该能够分辨出究竟是谁设置了 cookie:exmaple.com 引擎或 mysite.com 引擎

  2. 32字节长度是必须的,还是

  3. 我们应该能够加密时间戳(当 cookie 发出时)

  4. 生成的 cookie 值的第一个和最后一个字符应该不同,因此我们可以根据 cookie 进行 A/B 测试(因此我们总是可以说 cookie 的最后一个字符是否为“ > K", 显示此用户 "feature A")

考虑到生成的字符串的长度应始终为 32 个或更少的字符,并且数据应该加密和解密(当然不是由用户进行)并且字符串对于用户而言应该是唯一的,这使得任务变得相当复杂。

我的想法和问题:

我的第一个想法是将数据打包成二进制字符串,然后对其进行 base64 编码。结果将是 8 个字符长的 base64 编码字符串:

def encode():
    base64( pack('Lv', timestamp, microseconds) )

在开头和结尾添加站点发行者标志和字符:

def getCookie():
    rand('a'...'Z') + encode() + issuerFlagChar() + rand('a'...'Z')

所以,结果是 11 个字符长,我们很容易满足 约束 2

但问题是:这个算法肯定不安全,我不确定为数百万网站用户生成的字符串是否是唯一的。

我想知道是否可以为此目的使用 DES 或 AES,但我不确定生成的字符串是否始终满足 constraint 2(生成的字符串不应超过32 个 ascii 字符)。

是否有对称密钥算法可以确保 "if you encrypt N bytes with M-bytes key you will have resulting data length of Math.Ceil(N*2+1/M) bytes" 之类的东西?那么最终的长度是可以预测的吗?

抛开您确实应该咨询安全顾问这一事实不谈,您提出的实际问题很容易得到解答:

Is there symmetric key algorithms that ensure something like "if you encrypt N bytes with M-bytes key you will have resulting data length of Math.Ceil(N*2+1/M) bytes"? So the resulting length would be predictable?

是的,有。他们被称为Block Ciphers

根据定义,每个分组密码都有属性,即密文的长度等于明文的长度。在实践中,大多数分组密码(包括 DES 和 AES)都有一点作弊,因为它们在开始加密之前要求明文为 padded to the length of the block

换句话说,给定 N 字节的明文和 B 的块大小,密文的长度为 B*(Math.ceil(N/B)) 字节。

请注意我是如何谈论 块大小 的,它不同于 密钥大小 。在这种情况下,密钥大小实际上是无关紧要的。

例如,AES uses a block size of 128 bits,即 16 个字节。这意味着如果您的明文长度在 17 到 32 字节之间,AES 将保证您的密文长度为 32 字节。这与您选择的密钥大小无关,可以是 128、192 或 256 位(16、24 或 32 字节)之一。

首先,你需要知道你是想加密还是签名数据。

加密将阻止用户查看数据,但他们仍然可以根据加密类型以某些方式修改数据。例如,解密修改后的密文只会给出损坏的数据,不会失败。

另一方面,签名将阻止用户修改数据,也就是说,您的代码将能够检测到数据已被修改。一个简单的算法是 HMAC.

我假设你两者都想要。我下面的解决方案两者兼而有之。

您的 cookie 必须为 32 字节长,即 256 位。我们将为加密数据使用 128 位,为 HMAC 使用 128 位。

对于数据,我会将时间戳编码为 64 位整数(即使您想将其存储到微秒精度也绰绰有余)。如果您有两个站点,发布 cookie 的站点可以存储为 1 位,但我将其存储为 32 位整数,因为我们有很多 space。对于可用于 a/b 测试的标签也是如此。

所有数据正好是128位,16字节。这是 AES 块的确切大小。因此,我们将使用 AES 对其进行加密!

另外 16 个字节将是 MAC 的密文 (Encrypt then MAC)。我使用了 HMAC-SHA256,它有 256 位的输出。我们只有 128 位的空间,所以我截断了它。理论上这会降低它的安全性,但实际上 128 位足以使暴力尝试变得不可能。

解密cookie是相反的过程:计算给定密文的HMAC并检查它是否与给定的MAC匹配。如果是,继续解密密文并解包数据。

代码如下:

from struct import pack, unpack
from Crypto.Cipher import AES
import hashlib
import hmac


AES_KEY = hashlib.sha256(b"secret key 1 asdfasdf").digest()
HMAC_KEY = hashlib.sha256(b"secret key 2 asdfasdf").digest()

# timestamp: 64bit unix timestamp
# site: 32bit integer, which site issued the cookie
# tag: 32bit integer, tag used for a/b testing.
def encrypt_cookie(timestamp, site, tag):

    # Pack the data
    data = pack('QII', timestamp, site, tag)

    # Encrypt it
    aes = AES.new(AES_KEY, AES.MODE_ECB, 'This is an IV456')
    ciphertext = aes.encrypt(data)

    # Do HMAC of the ciphertext
    sig = hmac.new(HMAC_KEY, ciphertext, hashlib.sha256).digest()
    sig = sig[:16]   # Truncate to only first 16 bytes.

    return ciphertext + sig

def decrypt_cookie(cookie):

    # Do HMAC of the ciphertext
    sig = hmac.new(HMAC_KEY, cookie[:16], hashlib.sha256).digest()
    sig = sig[:16]   # Truncate to only first 16 bytes.

    # Check the HMAC is ok
    if sig != cookie[16:]:
        raise Exception("Cookie has been tampered with")

    # Decrypt it
    aes = AES.new(AES_KEY, AES.MODE_ECB, 'This is an IV456')
    data = aes.decrypt(cookie[:16])

    # unPack the data
    timestamp, site, tag = unpack('QII', data)

    return timestamp, site, tag

cookie = encrypt_cookie(1, 2, 3)
print(len(cookie))  # prints: 32
print(decrypt_cookie(cookie))  # prints: 1, 2, 3

# Change a single byte in the cookie, the last one
cookie = cookie[:31] + b'0'
print(decrypt_cookie(cookie))  # raises the exception

我很好奇为什么 cookie 必须是 32 字节。似乎是一个奇怪的要求,如果你没有它,你可以使用许多旨在解决这个问题的库,例如 Django signing 如果你使用的是 Django。