返回

文章详情

9位AES密钥的案例

Hacker News2026年10月28日 20:51

我们发现,几个PDF实现生成它们的AES-256文件密钥的状态空间足够小,以至于可以枚举。在MuPDF中,只有9位随机状态影响实际的AES-256密钥。其他PDF编辑器如Foxit将它们的AES-256密钥基于基于时间的随机性,因此也导致了容易恢复的密钥。 引言 PDF加密保护PDF文档中的字符串和数据流,包括元数据,除非明确保持未加密。密码保护机制经过多次修订,经历了从RC4到AES的转换,并加固了密码哈希以抵御暴力攻击。当前版本,修订版6,使用AES-256。当一个PDF用AES-256加密时,密码并不会直接加密文档。实现生成一个随机的32字节文件密钥,用于加密内容。然后,密码和盐被哈希以派生一个单独的密钥,用于加密这个随机的32字节文件密钥。修订版6故意使此哈希过程变慢;较早的AES-256修订版5则没有。一个PDF可以有一个用户密码用于查看,以及一个拥有者密码用于更改权限。任一密码都可以解锁同一个文件密钥,因此限制取决于读者:用查看密码,你也可以制作一个不受限制的副本。 我们发现,这种随机性通常生成得非常不安全,允许无密码解密受保护的PDF。 MuPDF 版本1.28.5之前的MuPDF使用drand48系列的实现来生成随机数。这个生成器只初始化一次,使用当前的时间戳(以秒为单位): static void fz_init_random_context ( fz_context * ctx ) { /* ... */ fz_srand48 ( ctx , ( uint32_t ) time ( NULL )); } 生成器是一个48位的线性同余生成器: sn+1=(𝟶𝚡𝟻𝚍𝚎𝚎𝚌𝚎𝟼𝟼𝚍sn+𝟶𝚡𝚋)mod248 fz_srand48()将状态写为三条16位字。低位字是常量0x330e,而32位时间戳占据状态位16到47: void fz_srand48 ( fz_context * ctx , int32_t seed ) { uint16_t p [ 3 ] = { 0x 330e , seed , seed >> 16 }; fz_seed48 ( ctx , p ); } 当MuPDF需要生成密钥时,它调用fz_memrnd(),该函数会反复获取一个随机数,向前步进随机数生成器,然后将这个随机数的最低字节用作随机字节。 int32_t fz_nrand48 ( fz_context * ctx , uint16_t s [ 3 ]) { return fz_rand48_step ( s , ctx -> seed48 + 3 ) >> 17 ; } /* 在fz_memrnd()中 */ while ( len -- > 0 ) * data ++ = ( unsigned char ) fz_lrand48 ( ctx ); 位移会丢弃状态位0到16,强制转换为unsigned char会丢弃高于位24的所有内容。因此,每个输出字节都来自LCG状态的位17到24。问题在于,状态的高位从不影响低位。乘法和加法可以将位向上进位,但不能向下。在输出字节来自位17到24的情况下,任何高于位24的内容都可以被忽略,即使在向前步进生成器后。这留下了能影响输出的25位。底部16位始终初始化为0x330e,因此仅来自时间戳的9位重要。时间戳相差512秒会产生完全相同的字节流。由于可能的密钥空间非常小,因此我们可以通过生成所有可能的32字节密钥并尝试解密来进行强行破解: def stream ( seed , count ): state = ( seed << 16 ) | 0x 330e out = bytearray () for _ in range ( count ): state = ( 0x 5deece66d * state + 0x b ) & (( 1 << 48 ) - 1 ) out . append (( state >> 17 ) & 0x ff ) return bytes ( out ) candidates = [ stream ( seed , 64 )[ 32 : 64 ] for seed in range ( 512 )] MuPDF将同一生成器用于文档/ID,存储在明文的尾部。当写入新文档时,它会首先从同一LCG绘制32字节,然后立即在之后采样解密密钥,因此/ID可以用作额外检查。在一个非常未优化的Python实现中,搜索所有512个流使用纯暴力破解大约需要11毫秒。 在一个测试文档上:time(NULL) mod 512 = 80 /ID[0] = 7628523958da31d6c21c90e39344b28e /ID[1] = 8be910bddaa1f1a542e5a27ceb115346 key = c3888ecc8ff554ac93acd689da2ecd6223cb760168622e8a0c44a533a037c212 该密钥在没有任何密码的情况下解密该文档。 Foxit Foxit PDF Reader 2026.1.3.36551将文件密钥推导为当前时间戳、32个未初始化字节和一个常量的哈希。实际上,这些未初始化的字节被填充为零,并没有增加任何随机性。 SHA-256(uint32_le(time(NULL)) || <32未初始化的字节> || "there") 或者作为一个等效的Python代码片段: from hashlib import sha256 key = sha256 ( timestamp . to_bytes ( 4 , "little" ) + bytes ( 32 ) + b "there" ). digest () /Perms权限块包含已知明文,因此候选检查为一个哈希和一个AES块解密,没有密码哈希和没有文档解密:

阅读全文 →

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

☕请我喝杯咖啡