首页
社区
课程
招聘
如何只输入少量用户密码而使用大量的密码
2020-1-3 23:18 20235

如何只输入少量用户密码而使用大量的密码

2020-1-3 23:18
20235

  要想达到此目的单纯一个接收密码的窗口是不行的,否则很容易被穷举攻击得逞。需要设置默认密码数组,可以取材于某随机函数,从某种子开始后面的数个字节。这个种子需要从程序的另一窗口取得,让其接收很少的几个字节,用它们算出种子的数值。

  这样程序得到用户输入的两个短密码,一个用于算出程序运行各个过程的参数,另一个算出默认密码的种子,用随机函数生成加密用的数组原形,对其进行数据处理形成密钥数组,用于加密或解密。

  程序设计中可以使用多个随机函数联合使用将增加破解难度。使用一个窗口输入也不是不行,让其接收两次数据即可。这样做都是为了增加可能的状态数让各种攻击不能得逞。

  也可以预设几组默认密码供用户选择,选择好后这些默认密码是附在用户输入密码之后合起来作为密码使用的,程序要用输入部分对附加部分做一些调整再作为用户密码使用,穷举攻击时必须要考虑到所有情况,这样就达到了少量输入而使用大量数组做密码的目的,这样可以有效的克制穷举攻击的骚扰。


下面举例说明如何设计:
  1)加密程序有两个窗口一个用于接收短密码,另一个接收一个数值,这个数值作为随机函数的种子,从此种子开始取和文件长度N一样多的字节数作为默认密码,不要嫌多,用其一部分即可。默认密码的长度是N,程序用短密码的数据加工出,一些参数用这些参数去控制程序,用参数组成一个种子用随机函数对默认密码数组进行随机排序,形成新的数组,这个新数组作为用户密码数组,程序可以用这个数组当做密钥数组进行加密或解密,也可以从此用户密码中再选出种子计算新数组作为密钥数组,方法自定。
  解密时,由于程序掌握了默认密码的数值,可以考察这些数值是否存在,如不存在报错,例如没有输入默认密码的种子,这个数值当然就不存在了。
  2)程序定义默认密码长度,但其中数组的内容,是有短密码的扩展而来。例如利用CRC函数可以将原数据无限制的扩展到需要的数目。

  上面就是输入一个短密码,而使用长密码数组的两个例子。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-6-6 02:31 被sjdkx编辑 ,原因: 丰富内容更新观点,丰富内容
收藏
点赞0
打赏
分享
最新回复 (37)
雪    币: 12500
活跃值: (3043)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
白菜大哥 2020-1-4 05:41
2
0
不懂,我的理解是,假设你一个软件,登陆密码是123456,不论你如何加密,但是你下次输入123456的时候都必须能够登陆上去。只要有了这点,人家一直穷举,只要穷举到了123456还是可以登陆进去,你的加密就算在复杂,也失去了意义。至于你说的硬盘id和随机数之类的,这些还是可以伪造的,硬盘id可以hook,随机数可以int3 hook单字节指令以后模拟运行,也可以hook住random函数。
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-1-5 00:08
3
0
密码字符多了,穷举需要近乎无穷大的时间,所以穷举攻击失效,本帖的方法就是讨论如何输入有限的一点点密码,而实际使用大量的数组做用户密码。仔细看看道理很简单的。也不需要伪造什么东西。
雪    币: 397
活跃值: (799)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wsy 2020-1-6 10:04
4
0
现有的多数都比你的扯淡话完善的多。
继续围观装疯卖傻。
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-1-6 14:42
5
0
  我说的方法不能用吗?什么叫装疯卖傻?你要是理解不了我可以说得更详细些。
  例如得到几个用户输入的密码字符,当然这些都是数据了,用这几个数算出几个参数不难吧,然后算出随机函数的种子值,以此做种子取例如1000个随机函数的值放到字节数组里,这也不难吧,我们就用1000个数据为基础做出用户密码,但实际上我们用不了那么多的数据作为用户密码,刚才不是算出了几个参数吗?随便取一个算出个大于1000的数,用此数除以1000得到一个余数N,原来的数组是 f[0] , f[1],...f[999],现在取用f[N]及其后面的几个数据作为加密用的用户密码即可。
  为什么这样做,主要为了克制穷举攻击而已,诸位有什么好办法请不吝赐教。

雪    币: 5887
活跃值: (3700)
能力值: ( LV10,RANK:163 )
在线值:
发帖
回帖
粉丝
yimingqpa 1 2020-1-8 14:51
6
0
按网上大多数人做法是 MD5/SHA 加几次密码再发服务器, 服务器判断对错。
防穷举可以像银行一样,错误几次后禁止登陆。
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-1-8 15:18
7
0
错误几次后禁止登陆,确实是个办法。但必须考虑最坏的情况,即窃密方能够使用穷举攻击,也能有相应的软件,在此条件下能抵抗的住才能实现安全加密。
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-2-3 17:38
8
0
使用长密码对分组密码加密用处不大,因为分组密码密钥就那几位,找到关键部位穷举攻击就不错。
但对于流密码加密使用长密码就很厉害了,因为密码长度没有约束。而穷举长密码就是个梦。
可以这样做:设定一个长密码作为默认密码,得到用户密码后,对默认密码进行改造,得到加密使用的密码。如果加密无需那么多密码,可以拣选其中部分使用。
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-20 19:44
9
0
额,楼主我想问一下,你这个长密码也是依据用户提供的短密码生成的吧
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-25 00:04
10
1

楼主,还记得你写的功能强大的自动密码文件加密软件吗?那个帖子不能回复就在此回复一下吧。提前说一下,我是个初学者。我粗略的分析了一下,你的算法加密强度非常低,所有自动生成的密钥都是基于GetTickCount()返回得32位值,无论自动密码和用户密码长度多少,破解最多需要尝试 pow(2,32)*1000*20 次就能得到结果。(其实根本不需要这么多次,因为GetTickCount()返回开机到现在经过的毫秒数,不会太大)
上式的1000是最大自动密钥长度(字节为单位),20是最长用户密码长度(字节单位)。
所谓不可破解就是假定破解者不知道算法和被加密文件格式,现实中需要严格保护加密算法的情况,使用的加密算法都是特供的,你我都没有能力开发。
下面提供我逆出来的加解密算法,并提供解密pe格式文件的简单例子。
ps:我在伪代码里发现了很多类似 (a ^ (a ^ b)) 的异或代码,不知道是反编译器的原因还是楼主真的不知道  (a ^ (a ^ b)) == b
ps:一切避开数论讨论加密原理的行为都是耍流氓
#include <cstdio>
#include <ctime>
#include <Windows.h>
#include <algorithm>
#pragma warning(disable:28159)
#pragma warning(disable:4996)

DWORD pKeySink[623];
DWORD dwKeyLengthInDword = 623;
DWORD dwGenerateKeyCount;

BYTE cbDataSink[1000];
bool cbKeyGenerated;

void GenerateKeySinkWithRandom(_In_ DWORD Random) {
    DWORD dwCurrentValue;
    dwGenerateKeyCount = 0;
    cbKeyGenerated = true;
    pKeySink[0] = Random;
    for (DWORD index = 0; index < 622; ++index) {
        dwCurrentValue = pKeySink[index];
        pKeySink[index + 1] = index + 0x6C078965 * (dwCurrentValue ^ (dwCurrentValue >> 30));
    }
}

void updateKeySink() {
    bool v4; // zf

    for (auto i = 0; i < dwKeyLengthInDword; ++i) {
        v4 = (LOBYTE(pKeySink[i + 1] % 624) & 1) == 0;
        pKeySink[i] = ((pKeySink[(i + 1) % 624] & 0x7fffffffu) >> 1) ^ pKeySink[i % 624];
        if (!v4)
            pKeySink[i] ^= 0x9908B0DF;
    }
    return;
}

DWORD GenerateRandomInKeySink() {
    int v1; // eax
    DWORD v2; // ecx
    DWORD v3; // edx
    DWORD result; // eax

    if (!cbKeyGenerated) GenerateKeySinkWithRandom(time(nullptr));
    
    v1 = dwGenerateKeyCount;
    if (!dwGenerateKeyCount)
    {
        updateKeySink();
        v1 = dwGenerateKeyCount;
    }
    v2 = ((((pKeySink[v1] >> 11) ^ pKeySink[v1]) & 0xFF3A58AD) << 7) ^ (pKeySink[v1] >> 11) ^ pKeySink[v1];
    v3 = (v1 + 1) % 0x270u;
    result = ((v2 & 0xFFFFDF8C) << 15) ^ v2 ^ ((((v2 & 0xFFFFDF8C) << 15) ^ v2) >> 18);
    dwGenerateKeyCount = v3;
    return result;
}

__forceinline BOOLEAN NTAPI RtlIsValidImageHeader(_In_ LPCVOID lpBufferToImageHeader) {
    auto dos = PIMAGE_DOS_HEADER(lpBufferToImageHeader);
    return (dos->e_magic == IMAGE_DOS_SIGNATURE) &&
        (dos->e_lfanew >= sizeof(IMAGE_DOS_HEADER)) &&
        (dos->e_lfanew < 0x400) &&
        (PIMAGE_NT_HEADERS(SIZE_T(lpBufferToImageHeader) + dos->e_lfanew)->Signature == IMAGE_NT_SIGNATURE);
}

VOID NTAPI RtlUpdateBuffer(
    _In_ DWORD dwKey,
    _In_ BOOLEAN Encrypt,
    _In_ DWORD dwUserKeyLength,
    _In_ DWORD dwAutoKeyLength,
    _In_reads_bytes_(dwCiphertextLength) LPCVOID lpCiphertext,
    _In_ DWORD dwCiphertextLength,
    _Out_writes_bytes_all_(dwCiphertextLength) LPVOID lpResult,
    _Out_writes_opt_(dwUserKeyLength) LPDWORD lpKey) {
    LPDWORD pAutoKey = new DWORD[1ull + dwAutoKeyLength];
    DWORD dwTemp = 0;

    RtlMoveMemory(lpResult, lpCiphertext, dwCiphertextLength);

    GenerateKeySinkWithRandom(dwKey);
    for (auto i = 0; i < dwUserKeyLength; ++i)
        cbDataSink[i] = GenerateRandomInKeySink();
    for (auto i = 10; i; --i)
        for (auto j = 0; j < dwUserKeyLength; ++j)
            std::swap(cbDataSink[j], cbDataSink[(j + GenerateRandomInKeySink()) % dwUserKeyLength]);

    cbDataSink[dwUserKeyLength] = 0;

    if (dwUserKeyLength > 0)
        for (auto i = 0; i < dwUserKeyLength; ++i)
            dwTemp += cbDataSink[i];
    GenerateKeySinkWithRandom(dwTemp);
    if (dwAutoKeyLength > 0) {
        for (auto i = 0; i < dwAutoKeyLength; ++i)
            pAutoKey[i] = GenerateRandomInKeySink();
        for (auto i = 0; i < dwAutoKeyLength; ++i)
            std::swap(pAutoKey[i], pAutoKey[(i + GenerateRandomInKeySink()) % dwAutoKeyLength]);
    }
    GenerateKeySinkWithRandom(dwTemp * dwTemp);
    if (dwCiphertextLength > 0) {
        dwTemp = dwCiphertextLength % dwAutoKeyLength;
        for (auto i = 0; i < dwCiphertextLength; ++i) {
            if (i == dwTemp) GenerateKeySinkWithRandom(pAutoKey[1]);
            LPBYTE(lpResult)[i] += (Encrypt ? 1 : -1) * GenerateRandomInKeySink();
        }
    }
    if (lpKey) {
        for (auto i = 0; i < dwUserKeyLength; ++i)
            lpKey[i] = cbDataSink[i];
    }
    delete[]pAutoKey;
}

#define RtlEncryptBuffer(_dwKey_, _dwUserKeyLength_, _dwAutoKeyLength, _lpCiphertext_, _dwCiphertextLength_, _lpResult_, _lpKey_)\
    RtlUpdateBuffer(_dwKey_, TRUE, _dwUserKeyLength_, _dwAutoKeyLength, _lpCiphertext_, _dwCiphertextLength_, _lpResult_, _lpKey_)
#define RtlDecryptBuffer(_dwKey_, _dwUserKeyLength_, _dwAutoKeyLength, _lpCiphertext_, _dwCiphertextLength_, _lpResult_, _lpKey_)\
    RtlUpdateBuffer(_dwKey_, FALSE, _dwUserKeyLength_, _dwAutoKeyLength, _lpCiphertext_, _dwCiphertextLength_, _lpResult_, _lpKey_)

BOOLEAN NTAPI RtlCrackPeFileEncryption(
    _Out_writes_bytes_(21*2) LPSTR lpUserPassword,
    _In_opt_ DWORD dwBeginRandom,
    _In_ LPCVOID lpBufferToCrack,
    _In_ DWORD dwBufferSize) {
    lpUserPassword[0] = '\0';

    BOOLEAN result = FALSE;
    LPVOID lpData = nullptr;
    const auto Length = sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS32);
    auto BeginRandom = dwBeginRandom;
    DWORD dwUserKey[2];

    if (dwBufferSize < Length)
        return FALSE;

    lpData = new char[dwBufferSize];
    while (BeginRandom & 0xffffffff) {
        RtlDecryptBuffer(BeginRandom, 2, 567, lpBufferToCrack, dwBufferSize, lpData, LPDWORD(&dwUserKey));
        if (RtlIsValidImageHeader(lpData)) {
            //found
            result = TRUE;
            sprintf(lpUserPassword, "%02X%02X", BYTE(dwUserKey[0]), BYTE(dwUserKey[1]));
            break;
        }
        --BeginRandom;
    }

    return result;
}

int main() {
    //BYTE test[4] = { 0x11,0x22,0x33,0x44 };
    //PDWORD key = nullptr;
    //calc(test, 4, key, &key, 2, 567);
    //calc(test, 4, key, nullptr, 2, 567);

    typedef struct _PE_HEADERS{
        IMAGE_DOS_HEADER dos;
        IMAGE_NT_HEADERS32 nt;
        _PE_HEADERS() {
            dos.e_magic = IMAGE_DOS_SIGNATURE;
            dos.e_lfanew = sizeof(dos);
            nt.Signature = IMAGE_NT_SIGNATURE;
        }
    }PE_HEADERS,*PPE_HEADERS;

    auto pImage = new PE_HEADERS;
    DWORD key[2];
    char szKey[42];
    RtlEncryptBuffer(GetTickCount(), 2, 567, pImage, sizeof(PE_HEADERS), pImage, LPDWORD(&key));
    if (!RtlCrackPeFileEncryption(szKey, GetTickCount(), pImage, sizeof(PE_HEADERS))) {
        //failed
        abort();
    }

    //check key[] and szKey

    //success
    delete pImage;
    return 0;
}

雪    币: 8290
活跃值: (4826)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
v0id_ 2020-3-25 08:08
11
0
楼上说的对,如果要证明你的密码强度大一定要给出严谨的数学证明
雪    币: 9373
活跃值: (3306)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
Lightal 2 2020-3-25 09:01
12
0
有种民科既视感
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-3-25 16:24
13
0
@Boring勇哥 自动密码没有位数的限制,你那个20位是怎么来的?想让程序生成多少位字节密码是使用者自己设定的。 由于密码是程序随机生成的,所以加密强度极高,我是自夸了。这些密码信息不是保存在密文里(像一般加盐技术那样)而是由用户自行保存,所以相当安全,破解者想要得到比登天还难。
@Lightal 业余爱好,数学功底很差。
重申长密码的使用:
程序内部有一个较长的固定数组F,而 用户输入的密码 到达以后,程序将首先使用 用户输入的密码进行计算并用数据调整前面那个数组F,使其发生彻底改变生成F1,程序将使用这些改变后的数组F1作为用户密码来加密明文等。
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-25 22:15
14
0
截图为证。即使没有这些限制,复杂度也只是线性增长,并非指数增长,因此安全性不会提高太多。
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-25 22:17
15
0
只要程序第一次调用的GetTickCount()或者time()返回值确定了,后面所有的随机数数组的内容都确定了。
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-3-26 00:15
16
0
确定了又如何,能得到数据才是真章,否则还是0?
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-26 00:41
17
0
?????那不是你加密用的自动生成的密钥?有了密钥不就能解出明文了?看来是我水平太低,无法理解你的高级算法,楼主加油吧
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-3-27 00:14
18
0
您需要将自动密码的那个程序了解清楚,那个程序是这样工作的,你用它加密文件时:
给它你要加密的文件,它将自动生成用户密码(密码位数是事先设置的),它用这些用户密码完成加密,并给你显示用户密码让你保存,以便解密时用。特殊之处是不用您自己提供密码,程序代劳了。
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-27 10:05
19
0
我知道,程序算密码用的第一个随机数是一个32位的整数,只要确定了这个数,你后面所有的密钥都可以推算出来,加密还有什么意义?我又没说用户提供密码吧?不要偷换概念
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-27 10:08
20
0
你一直在回避算法的安全性,回答的无关紧要,不要回复我了,节约大家的时间,谢谢。
雪    币: 11
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_ggMM 2020-3-30 10:12
21
0
输入少量的用户密码 =》 我爆破少量密码就好了,省心!感谢楼主让我们吃上饭
雪    币: 4119
活跃值: (1500)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Boring勇哥 2020-3-30 16:45
22
0
wx_ggMM 输入少量的用户密码 =》 我爆破少量密码就好了,省心!感谢楼主让我们吃上饭
哈哈哈
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-3-30 19:21
23
0
别做梦了,程序设计就是使用大量密码的,请注意其默认的密码数组是预先设定好的,用户输入的少量密码,将对那个固定数组的数值进行全面改造,改造后的数组才是真正要使用的用户密码数组。
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_gnaidlth 2020-5-3 09:45
24
0
看着很有意思,专门注册了账号...
楼主能不能给举个例子,假如短密码只有1位,按照你的方法生成的长密码有多少种可能?然后再依次类推,短密码有2位、3位...时,长密码分别是多少种情况?
雪    币: 10014
活跃值: (2012)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
sjdkx 2020-5-3 20:27
25
0
楼上知道程序可以有默认密码吧,这个可以随便定义长度并无限制,设这个字符串为A串,这个东西是表面上的,如果读懂了程序你可以知道A串是什么,现在程序接收到用户的短密码,例如一两个字节,用这两个字节算出一个数值作为随机函数的种子,然后用随机函数控制对 A串进行随机排序,这时您了解的 A串将发生天翻地覆的变化,然后您就可以用这个变化后的 A串,当作用户密码来用了,这就是少量输入使用大量密码的秘密。
游客
登录 | 注册 方可回帖
返回