使用弱密钥暴力破解 DES
Posted
技术标签:
【中文标题】使用弱密钥暴力破解 DES【英文标题】:Brute forcing DES with a weak key 【发布时间】:2012-02-02 19:57:17 【问题描述】:我正在学习密码学课程,但被困在作业中。说明如下:
明文 plain6.txt 已使用 DES 加密为 encrypt6.dat,使用 64 位密钥作为 8 个字符的字符串(64 其中每 8 位被忽略的位),所有字符都是字母 (小写或大写)和数字(0 到 9)。
要完成任务,请在 2 月之前将加密密钥发送给我 12 日,23.59。
注意:我希望得到一个 8 字节(64 位)的密钥。每个字节应 与我的密钥中的相应字节重合,除了最少的 DES 中未使用的有效位,因此可能是任意的。
这是我第一次尝试 Python 的代码:
import time
from Crypto.Cipher import DES
class BreakDES(object):
def __init__(self, file, passwordLength = 8, testLength = 8):
self.file = file
self.passwordLength = passwordLength
self.testLength = testLength
self.EncryptedFile = open(file + '.des')
self.DecryptedFile = open(file + '.txt')
self.encryptedChunk = self.EncryptedFile.read(self.testLength)
self.decryptedChunk = self.DecryptedFile.read(self.testLength)
self.start = time.time()
self.counter = 0
self.chars = range(48, 58) + range(65, 91) + range(97, 123)
self.key = False
self.broken = False
self.testPasswords(passwordLength, 0, '')
if not self.broken:
print "Password not found."
duration = time.time() - self.start
print "Brute force took %.2f" % duration
print "Tested %.2f per second" % (self.counter / duration)
def decrypt(self):
des = DES.new(self.key.decode('hex'))
if des.decrypt(self.encryptedChunk) == self.decryptedChunk:
self.broken = True
print "Password found: 0x%s" % self.key
self.counter += 1
def testPasswords(self, width, position, baseString):
for char in self.chars:
if(not self.broken):
if position < width:
self.testPasswords(width, position + 1, baseString + "%c" % char)
self.key = (baseString + "%c" % char).encode('hex').zfill(16)
self.decrypt()
# run it for password length 4
BreakDES("test3", 4)
我的速度达到了 60.000 次/秒。 62 个字符的 8 个字节的密码提供了 13 万亿种可能性,这意味着以这种速度,我需要 130 年才能解决。我知道这不是一个有效的实现,而且我可以用更快的语言(如 C 或它的风格)获得更快的速度,但我从未在这些语言中编程过。即使我得到了 10 的加速,我们仍然距离每秒 10,000,000,000 次进入小时范围还有一个巨大的飞跃。
我错过了什么?这应该是一个弱键:)。嗯,比完整的 256 个字符集弱。
编辑
由于对分配的一些模糊性,这里是完整的描述和一些用于校准的测试文件:http://users.abo.fi/ipetre/crypto/assignment6.html
编辑 2
这是一个粗略的 C 实现,在 i7 2600K 上每个内核每秒可获取大约 2.000.000 个密码。您必须指定密码的第一个字符,并且可以在不同的内核/计算机上手动运行多个实例。我设法在四台计算机上使用它在几个小时内解决了这个问题。
#include <stdio.h> /* fprintf */
#include <stdlib.h> /* malloc, free, exit */
#include <unistd.h>
#include <string.h> /* strerror */
#include <signal.h>
#include <openssl/des.h>
static long long unsigned nrkeys = 0; // performance counter
char *
Encrypt( char *Key, char *Msg, int size)
static char* Res;
free(Res);
int n=0;
DES_cblock Key2;
DES_key_schedule schedule;
Res = ( char * ) malloc( size );
/* Prepare the key for use with DES_ecb_encrypt */
memcpy( Key2, Key,8);
DES_set_odd_parity( &Key2 );
DES_set_key_checked( &Key2, &schedule );
/* Encryption occurs here */
DES_ecb_encrypt( ( unsigned char (*) [8] ) Msg, ( unsigned char (*) [8] ) Res,
&schedule, DES_ENCRYPT );
return (Res);
char *
Decrypt( char *Key, char *Msg, int size)
static char* Res;
free(Res);
int n=0;
DES_cblock Key2;
DES_key_schedule schedule;
Res = ( char * ) malloc( size );
/* Prepare the key for use with DES_ecb_encrypt */
memcpy( Key2, Key,8);
DES_set_odd_parity( &Key2 );
DES_set_key_checked( &Key2, &schedule );
/* Decryption occurs here */
DES_ecb_encrypt( ( unsigned char (*) [8]) Msg, ( unsigned char (*) [8]) Res,
&schedule, DES_DECRYPT );
return (Res);
void ex_program(int sig);
int main(int argc, char *argv[])
(void) signal(SIGINT, ex_program);
if ( argc != 4 ) /* argc should be 2 for correct execution */
printf( "Usage: %s ciphertext plaintext keyspace \n", argv[0] );
exit(1);
FILE *f, *g;
int counter, i, prime = 0, len = 8;
char cbuff[8], mbuff[8];
char letters[] = "02468ACEGIKMOQSUWYacegikmoqsuwy";
int nbletters = sizeof(letters)-1;
int entry[len];
char *password, *decrypted, *plain;
if(atoi(argv[3]) > nbletters-2)
printf("The range must be between 0-%d\n", nbletters-2);
exit(1);
prime = atoi(argv[1])
// read first 8 bytes of the encrypted file
f = fopen(argv[1], "rb");
if(!f)
printf("Unable to open the file\n");
return 1;
for (counter = 0; counter < 8; counter ++) cbuff[counter] = fgetc(f);
fclose(f);
// read first 8 bytes of the plaintext file
g = fopen(argv[2], "r");
if(!f)
printf("Unable to open the file\n");
return 1;
for (counter = 0; counter < 8; counter ++) mbuff[counter] = fgetc(g);
fclose(g);
plain = malloc(8);
memcpy(plain, mbuff, 8);
// fill the keys
for(i=0 ; i<len ; i++) entry[i] = 0;
entry[len-1] = prime;
// loop until the length is reached
do
password = malloc(8);
decrypted = malloc(8);
// build the pasword
for(i=0 ; i<len ; i++) password[i] = letters[entry[i]];
nrkeys++;
// end of range and notices
if(nrkeys % 10000000 == 0)
printf("Current key: %s\n", password);
printf("End of range ");
for(i=0; i<len; i++) putchar(letters[lastKey[i]]);
putchar('\n');
// decrypt
memcpy(decrypted,Decrypt(password,cbuff,8), 8);
// compare the decrypted with the mbuff
// if they are equal, exit the loop, we have the password
if (strcmp(mbuff, decrypted) == 0)
printf("We've got it! The key is: %s\n", password);
printf("%lld keys searched\n", nrkeys);
exit(0);
free(password);
free(decrypted);
// spin up key until it overflows
for(i=0 ; i<len && ++entry[i] == nbletters; i++) entry[i] = 0;
while(i<len);
return 0;
void ex_program(int sig)
printf("\n\nProgram terminated %lld keys searched.\n", nrkeys);
(void) signal(SIGINT, SIG_DFL);
exit(0);
【问题讨论】:
也许读过 DES 的结构。存在基于信息如何通过通道传播的漏洞。 太棒了,没有等到前一天晚上。 如果你有钱,并且你真的想暴力破解,考虑一下你可以创建 10^16 个 amazon ec2 工作,每个工作只用分配的密钥加密给定的明文。 您可以随时尝试在the crypto stack exchange 上发布此内容。 This guy 询问算法初始化向量的弱点。也许他们学到了一些东西? 【参考方案1】:我会假设所需的解决方案是实际实现算法。然后,由于您正在解密自己,您可以提前保释,假设纯文本也是 A-Za-z0-9,那么在解密单个字节后,您有 98% 的机会能够停止,即 99.97%解密 2 个字节后停止的几率,以及 3 个字节后停止的几率为 99.9995%。
另外,使用 C 或 Ocaml 或类似的东西。与进行加密相比,您可能花费更多时间进行字符串操作。或者,至少使用多处理并启动所有内核......
【讨论】:
+1。对于 C 中现有的免费实现,请查看此处:linux.die.net/man/3/ecb_crypt 仅按原样使用现有的 C 库,可能不会比 python Crypto 快得多。 可能,但他生成密钥的代码位几乎肯定会杀死他。 关于搜索整个密钥空间而不产生开销的高效算法的任何提示? 在 C 中使用 64 位无符号应该不会太难,使用底部 56 位作为计数器,然后复制到第二个长度并进行一些移位以获得实际的 64 位密钥。 DES 是 Feistel 密码,提早停止确实无法节省太多时间。也许 6%。【参考方案2】:有一个明显的因素 256 加速:每字节一位不是密钥的一部分。 DES 只有一个 56 位的密钥,但有一个 64 位的密钥。找出它是哪个位,并丢弃等效的字符。
【讨论】:
【参考方案3】:我得到了很多帮助,这是 C 中的一个解决方案。由于我是 C 初学者,它可能充满了错误和不好的做法,但它确实有效。
正如 CodeInChaos 发现的那样,只需要测试 31 个字符,因为 DES 忽略密钥的每个第 8 位,例如 ASCII 字符 b: *0110001*0
和 c: *0110001*1
在用作加密/解密的一部分时相同键。
我正在使用 OpenSSL 库进行 DES 解密。在我的机器上,它达到的速度约为每秒 180 万个密码,这使得测试整个密钥空间的总时间约为 5 天。这比最后期限还差一天。比上面的 Python 代码要好得多。
还有改进的余地,可能代码可以优化和线程化。如果我可以使用我所有的内核,我估计时间会减少到一天多一点,但是我还没有线程方面的经验。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <openssl/des.h>
static long long unsigned nrkeys = 0; // performance counter
char *
Encrypt( char *Key, char *Msg, int size)
static char* Res;
free(Res);
int n=0;
DES_cblock Key2;
DES_key_schedule schedule;
Res = ( char * ) malloc( size );
/* Prepare the key for use with DES_ecb_encrypt */
memcpy( Key2, Key,8);
DES_set_odd_parity( &Key2 );
DES_set_key_checked( &Key2, &schedule );
/* Encryption occurs here */
DES_ecb_encrypt( ( unsigned char (*) [8] ) Msg, ( unsigned char (*) [8] ) Res,
&schedule, DES_ENCRYPT );
return (Res);
char *
Decrypt( char *Key, char *Msg, int size)
static char* Res;
free(Res);
int n=0;
DES_cblock Key2;
DES_key_schedule schedule;
Res = ( char * ) malloc( size );
/* Prepare the key for use with DES_ecb_encrypt */
memcpy( Key2, Key,8);
DES_set_odd_parity( &Key2 );
DES_set_key_checked( &Key2, &schedule );
/* Decryption occurs here */
DES_ecb_encrypt( ( unsigned char (*) [8]) Msg, ( unsigned char (*) [8]) Res,
&schedule, DES_DECRYPT );
return (Res);
void ex_program(int sig);
int main()
(void) signal(SIGINT, ex_program);
FILE *f, *g; // file handlers
int counter, i, len = 8; // counters and password length
char cbuff[8], mbuff[8]; // buffers
char letters[] = "02468ACEGIKMOQSUWYacegikmoqsuwy"; // reduced letter pool for password brute force
int nbletters = sizeof(letters)-1;
int entry[len];
char *password, *decrypted;
// read first 8 bytes of the encrypted file
f = fopen("test2.dat", "rb");
if(!f)
printf("Unable to open the file\n");
return 1;
for (counter = 0; counter < 8; counter ++) cbuff[counter] = fgetc(f);
fclose(f);
// read first 8 bytes of the plaintext file
g = fopen("test2.txt", "r");
if(!f)
printf("Unable to open the file\n");
return 1;
for (counter = 0; counter < 8; counter ++) mbuff[counter] = fgetc(g);
fclose(g);
// fill the initial key
for(i=0 ; i<len ; i++) entry[i] = 0;
// loop until the length is reached
do
password = malloc(8);
decrypted = malloc(8);
// build the pasword
for(i=0 ; i<len ; i++) password[i] = letters[entry[i]];
nrkeys++;
if(nrkeys % 10000000 == 0)
printf("Current key: %s\n", password);
// decrypt
memcpy(decrypted,Decrypt(password,cbuff,8), 8);
// compare the decrypted with the mbuff
// if they are equal, exit the loop, we have the password
if (strcmp(mbuff, decrypted) == 0)
printf("We've got it! The key is: %s\n", password);
printf("%lld keys searched", nrkeys);
exit(0);
free(password);
free(decrypted);
// spin up key until it overflows
for(i=0 ; i<len && ++entry[i] == nbletters; i++) entry[i] = 0;
while(i<len);
return 0;
void ex_program(int sig)
printf("\n\nProgram terminated %lld keys searched.\n", nrkeys);
(void) signal(SIGINT, SIG_DFL);
exit(0);
【讨论】:
只需将程序调整为仅在部分键空间(由参数给出)上工作,然后并行调用它多次,例如一次用于以 A-M 开头的键,一次用于 N-Z,一次用于 a-m,一次用于 n-z,一次用于 0-9 ......当然,使用相同大小的部分,并且只要你有核心的次数(或者少一个,所以你仍然可以使用您的计算机)。这里不需要花哨的多线程。【参考方案4】:我不禁注意到作业的措辞:实际上并没有要求您自己提供 DES 实现或破解程序。如果确实如此,何不看看John the Ripper 或hashcat 之类的工具。
【讨论】:
我也注意到了,但我想知道如果不实施 DES 破解,分配的意义何在。 不需要提供实现,只需提供密钥即可,但最终分配将破坏完整的 DES。这是一场实现最大性能的竞赛。您可以使用 OpenCL 并租用 Amazon GPU 计算云、进行分布式攻击或租用 COPACOBANA。但这些有点超出我的范围:) 破解完整的 DES 听起来更像是“投入足够的钱”,而不是任何类型的加密或编码挑战。 @CodeInChaos:是的,作业中显然有更多相关信息,OP 没有向我们展示。 @element:我现在明白了。在这种情况下,这个答案和 CodeInChaos 的答案的结合是直接的。 CodeInChaos 的答案告诉您为什么每个角色不是 62 种可能性,而是只有 31 种可能性。总可能性略低于 1 万亿,应该可以通过 John The Ripper 中的代码实现。【参考方案5】:这个答案可能是对其他更具体建议的补充,但您应该做的第一件事是运行 profiler。
这里有很多很好的例子:
How can you profile a python script?
编辑:
对于这个特殊的任务,我意识到这无济于事。 10 GHz 的试验频率是……在频率低于该频率的单台机器上很难。也许您可以提及您可用的硬件。 另外,不要打算在几个小时内运行它。当您找到一种方法可以在一周内提供合理的成功概率时,您就可以让它运行,同时改进您的方法。
【讨论】:
Intel i7 2600K,8 GB RAM,GTX 580 是我的主要机器。我运行了分析器,实际上密钥生成占用了大部分时间,我现在正在重写那部分。以上是关于使用弱密钥暴力破解 DES的主要内容,如果未能解决你的问题,请参考以下文章