49

一段时间以来,我一直想知道某些软件如何以无法轻易发现的方式隐藏密钥。仅举几个例子:

  • DVD 播放器软件隐藏 CSS 键
  • 带有序列号/注册码的软件隐藏了用于验证序列号的密钥/哈希

显然,这些程序所做的不仅仅是将密钥放在 byte[] 中,因为这样可以很容易地窃取他们的密钥并生成您自己的序列号等。

使用什么样的策略来隐藏这些键,使它们不容易被发现?

4

11 回答 11

39

这些密钥如此容易被发现的原因是它们隐藏在软件中。

不惜一切代价避免在软件中隐藏秘密——混淆只会让你走这么远。问问自己这个问题:对于完全访问反汇编、用户模式和内核模式调试器但没有日常工作的人,我能在多大程度上隐藏软件中的密钥?它被破解只是时间问题。

于 2009-04-18T19:35:00.127 回答
13

您只需将密钥隐藏在某处,并在需要时对其进行解密。“安全地”使用密钥是复杂的部分。破解者可能会在您使用解密密钥的位置设置断点并将其转储。他们可能会扫描您的代码以查找表明您正在使用已知加密算法的模式(大多数算法都有预先计算的表)。等等等等

这就是为什么您需要使整个软件的可执行文件难以分析。为此,您使用可执行打包程序、在虚拟机中运行代码、完整性检查等。所有这些都是为了减慢调试和修改代码的速度。

正如这里的大多数人指出的那样,您无法阻止任何人,只能让他们慢下来。我会去一个破解者论坛并在那里询问有关关键隐藏问题的建议。如果您问得好,它们很可能会有所帮助。

附言。公钥加密不会更好地隐藏密钥,但如果您正在执行许可方案,它可能会使密钥生成器更难(或理论上不可能)。

于 2009-04-18T23:02:14.593 回答
6

底线是,你不能。有关原因,请参阅此处的任何其他评论。甚至像 PGP/GPG 这样的加密软件也将密钥存储在一个文件中,然后强烈要求将这些文件保存在安全的闪存驱动器或其他安全的地方。将发现作为可执行代码的一部分存储的密钥。

事实上,如果你试图在客户端机器上加密任何将被客户端解密作为正常操作的一部分的东西,那也是愚蠢的差事。客户端机器本质上是不安全的,您无法控制它们将能够对您的数据做什么。

相反,如果您尝试进行身份验证,请查看基于 Internet 的身份验证以及登录到服务器,或某种生成的用于验证软件的 KeyCode。

作为公钥-私钥对的一部分的密钥应该保存在可以保护的数据文件中。对称密钥应作为会话密钥即时生成,然后丢弃。始终假设在其计算机上拥有 Secret 或 Session 密钥的任何人都能够发现它,并违背您的意图使用它。

阅读Bruce Schneier 的“应用密码学”以获取更多信息。

于 2009-04-18T19:53:46.230 回答
5

你不能永远隐藏一把钥匙。但你可以肯定很难找到。一些方法是在内存中加密密钥,保留多个副本(可能以不同方式加密)相互检查,留下可供访问的虚拟副本,以某种奇怪的格式存储密钥等。如果有人真的这样做,它们都不会起作用想要你的钥匙,但你至少可以劝阻一个随便/没有经验的攻击者。

于 2009-04-18T19:45:29.777 回答
1

您并不总是需要密钥来验证许可证。

但忽略这一事实,您的密钥也可能是另一个函数的结果。您实际上并没有存储特定的键值,而是有一个动态生成键的函数(总是相同的结果)。尽管并非不可能,但由于您不再寻找值,因此要找到它要困难得多,但是您必须弄清楚它是一个方程式。

于 2009-04-18T19:35:54.883 回答
1

当我们开始开发我们的软件时,我们已经创建了一个注明日期的许可证文件。然后,我们意识到,甚至没有太多人有兴趣购买我们的软件。然后,我们决定免费赠送。更多的人开始关心至少尝试我们的杰作。最后,我们开源了我们的软件。更多的用户开始使用它。现在我们只是希望这些用户中的一小部分可能会变成付费客户(即购买产品支持或要求定制)。

底线是,如果有人想破解您的软件,他/她无论如何都会这样做。浪费时间试图用这个隐藏的密钥保护它真的值得吗?

于 2009-04-18T19:42:56.647 回答
1

如果您负担得起,最好将私钥存储在加密 USB 令牌中。关键是只写,即您可以设置它但不能读取它。令牌在其硬件内部进行加密操作。检索密钥变得非常复杂(如果令牌没有已知的漏洞,那么旧的情况就不是这样)。

于 2016-05-19T14:07:56.763 回答
1

您可以使用https://godbolt.org/查看程序集并查看,您可以采取一些措施使其变得更加困难。首先是隐藏符号(所以checkSecret()不那么明显),其次是使用函数来生成密钥/密码。这个想法是找到将导致绕过任何安全性的代码部分需要更长的时间,因此也不从main函数中调用它可能是一个好主意。

看两种方法:

  1. 明确声明的密码
  2. 生成密码的函数

这是第一个示例:

#include <string>
#include <iostream>

using namespace std;

string secretKey = "fdasfdasfydsafhidljj3r32R#@f";

bool securityCheck(string key){
    if(key==secretKey) cout << "Success!" << endl;
    return key==secretKey;
}

int main(){
    securityCheck(secretKey);
}

正如您在https://godbolt.org/上更清楚地看到的那样,这将创建以下程序集(使用 gcc 11.2):

secretKey[abi:cxx11]:
        .zero   32
.LC0:
        .string "Success!"
securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     rdi, rax
        call    __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
        test    al, al
        je      .L16
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
.L16:
        mov     rax, QWORD PTR [rbp-8]
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     rdi, rax
        call    __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 40
        lea     rax, [rbp-48]
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) [complete object constructor]
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        mov     eax, 0
        jmp     .L22
        mov     rbx, rax
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        mov     rax, rbx
        mov     rdi, rax
        call    _Unwind_Resume
.L22:
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret
.LC1:
        .string "basic_string::_M_construct null not valid"

.LC2:
        .string "fdasfdasfydsafhidljj3r32R#@f"
__static_initialization_and_destruction_0(int, int):
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 40
        mov     DWORD PTR [rbp-36], edi
        mov     DWORD PTR [rbp-40], esi
        cmp     DWORD PTR [rbp-36], 1
        jne     .L58
        cmp     DWORD PTR [rbp-40], 65535
        jne     .L58
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        call    __cxa_atexit
        lea     rax, [rbp-17]
        mov     rdi, rax
        call    std::allocator<char>::allocator() [complete object constructor]
        lea     rax, [rbp-17]
        mov     rdx, rax
        mov     esi, OFFSET FLAT:.LC2
        mov     edi, OFFSET FLAT:secretKey[abi:cxx11]
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
        lea     rax, [rbp-17]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     edi, OFFSET FLAT:std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        call    __cxa_atexit
        jmp     .L58
        mov     rbx, rax
        lea     rax, [rbp-17]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
        mov     rax, rbx
        mov     rdi, rax
        call    _Unwind_Resume
.L58:
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret
_GLOBAL__sub_I_secretKey[abi:cxx11]:
        push    rbp
        mov     rbp, rsp
        mov     esi, 65535
        mov     edi, 1
        call    __static_initialization_and_destruction_0(int, int)
        pop     rbp
        ret

您可以看到secretKey是清晰可见的,函数名称也是如此。

这是第二个例子:

#include <string>
#include <iostream>

using namespace std;

string getSecretKey(){
    srand(100);
    string chars = "qwertyuioplkjhgfdsazxcvbnm123456789";
    string result = "";
    for(int i = 0; i < 100; ++i){
        result += chars[rand()%chars.size()];
    }
    return result;
}

string secretKey = getSecretKey();

bool securityCheck(string key){
    if(key==secretKey) cout << "Success!" << endl;
    return key==secretKey;
}

int main(){
    securityCheck(secretKey);
}

这将产生以下程序集:

.LC0:
        .string "qwertyuioplkjhgfdsazxcvbnm123456789"
.LC1:
        .string ""
getSecretKey[abi:cxx11]():
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 72
        mov     QWORD PTR [rbp-72], rdi
        mov     edi, 100
        call    srand
        lea     rax, [rbp-22]
        mov     rdi, rax
        call    std::allocator<char>::allocator() [complete object constructor]
        lea     rdx, [rbp-22]
        lea     rax, [rbp-64]
        mov     esi, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
        lea     rax, [rbp-22]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
        lea     rax, [rbp-21]
        mov     rdi, rax
        call    std::allocator<char>::allocator() [complete object constructor]
        lea     rdx, [rbp-21]
        mov     rax, QWORD PTR [rbp-72]
        mov     esi, OFFSET FLAT:.LC1
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<std::allocator<char> >(char const*, std::allocator<char> const&)
        lea     rax, [rbp-21]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
        mov     DWORD PTR [rbp-20], 0
        jmp     .L16
.L17:
        call    rand
        movsx   rbx, eax
        lea     rax, [rbp-64]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::size() const
        mov     rcx, rax
        mov     rax, rbx
        mov     edx, 0
        div     rcx
        mov     rcx, rdx
        mov     rdx, rcx
        lea     rax, [rbp-64]
        mov     rsi, rdx
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator[](unsigned long)
        movzx   eax, BYTE PTR [rax]
        movsx   edx, al
        mov     rax, QWORD PTR [rbp-72]
        mov     esi, edx
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator+=(char)
        add     DWORD PTR [rbp-20], 1
.L16:
        cmp     DWORD PTR [rbp-20], 99
        jle     .L17
        nop
        lea     rax, [rbp-64]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        jmp     .L26
        mov     rbx, rax
        lea     rax, [rbp-22]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
        mov     rax, rbx
        mov     rdi, rax
        call    _Unwind_Resume
        mov     rbx, rax
        lea     rax, [rbp-21]
        mov     rdi, rax
        call    std::allocator<char>::~allocator() [complete object destructor]
        jmp     .L21
        mov     rbx, rax
        mov     rax, QWORD PTR [rbp-72]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
.L21:
        lea     rax, [rbp-64]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        mov     rax, rbx
        mov     rdi, rax
        call    _Unwind_Resume
.L26:
        mov     rax, QWORD PTR [rbp-72]
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret
secretKey[abi:cxx11]:
        .zero   32
.LC2:
        .string "Success!"
securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     rdi, rax
        call    __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
        test    al, al
        je      .L28
        mov     esi, OFFSET FLAT:.LC2
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
.L28:
        mov     rax, QWORD PTR [rbp-8]
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     rdi, rax
        call    __gnu_cxx::__enable_if<std::__is_char<char>::__value, bool>::__type std::operator==<char>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 40
        lea     rax, [rbp-48]
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) [complete object constructor]
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    securityCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        mov     eax, 0
        jmp     .L34
        mov     rbx, rax
        lea     rax, [rbp-48]
        mov     rdi, rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        mov     rax, rbx
        mov     rdi, rax
        call    _Unwind_Resume
.L34:
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret
.LC3:
        .string "basic_string::_M_construct null not valid"

__static_initialization_and_destruction_0(int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        cmp     DWORD PTR [rbp-4], 1
        jne     .L72
        cmp     DWORD PTR [rbp-8], 65535
        jne     .L72
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        call    __cxa_atexit
        mov     eax, OFFSET FLAT:secretKey[abi:cxx11]
        mov     rdi, rax
        call    getSecretKey[abi:cxx11]()
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:secretKey[abi:cxx11]
        mov     edi, OFFSET FLAT:std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        call    __cxa_atexit
.L72:
        nop
        leave
        ret
_GLOBAL__sub_I_getSecretKey[abi:cxx11]():
        push    rbp
        mov     rbp, rsp
        mov     esi, 65535
        mov     edi, 1
        call    __static_initialization_and_destruction_0(int, int)
        pop     rbp
        ret

您绝对可以看到您不再天真地扫描代码以查找有趣的字符串,但涉及的内容更多。您必须设置断点并检查不同的值,并尝试从逻辑上查看它以解决它。尽管如此,在此示例中,符号是可见的,这会泄露有关应用程序正在做什么的大量敏感信息。从您所看到的情况来看,隐藏符号会使绕过安全性变得更加困难。

于 2021-11-11T02:33:51.717 回答
0

在代码中隐藏密钥不会真正安全。您可能已经注意到 DVD 和大多数软件序列号注册每天都会被黑客入侵。如果你真的想保护你需要使用公钥加密的东西。

于 2009-04-18T19:38:08.390 回答
0

你不能,这是不可能的。任何有权访问目标机器的攻击者都可以反汇编您的代码以找到它,或者在目标机器上找到密钥文件等。

确保加密密钥安全的唯一方法是在需要时由用户手动输入。

于 2017-12-22T18:59:56.377 回答
-2

我认为这是 DVD 和 BluRay 如此迅速破解的最大原因之一。我认为他们能够真正阻止普通人以数字方式复制家庭电影的唯一方法是,如果他们创建了一种未经许可在计算机上使用且只能在经过认证的播放器上使用的媒体。将切断想要在他们的计算机和笔记本电脑上观看电影的市场部分,但可能会在更长时间内停止拥有完美的数字撕裂,并且会阻止普通人能够做到这一点。

于 2009-04-18T19:50:49.747 回答