【问题标题】:How do you hide secret keys in code?如何在代码中隐藏密钥?
【发布时间】:2009-04-18 19:32:19
【问题描述】:

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

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

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

使用了哪些策略来隐藏这些键,使它们不易被发现?

【问题讨论】:

  • 但是 Adob​​e 是如何使用 Photoshop 来做到这一点的呢?
  • 一个字:不成功。

标签: encryption obfuscation


【解决方案1】:

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

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

【讨论】:

  • 这能回答问题吗?
  • 正如您所说,“破解只是时间问题”。因此,考虑到此密钥将比受硬件保护的密钥更快过期,这就是为什么该策略可能是可接受的原因。
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

    阅读 Bruce Schneier 的“Applied Cryptography”了解更多信息。

    【讨论】:

    • “即使是像 PGP/GPG 这样的加密软件也将密钥存储在文件中”的说法具有误导性。 PGP 使用只有用户知道的密码来加密这些密钥。 PGP 还为密钥使用安全内存,以确保它们永远不会被缓存到 HD 中,并且 PGP 会经过很长的时间来使使用内存检查很难找到密钥。如果我可以修改这个答案,我会的。
    • 我的意思是源代码中没有存储秘密数据。保护自己的密钥由用户决定。即使在密钥上有密码,一旦恶意方访问了密钥文件,游戏就结束了。将密钥存储在除受信任方之外的任何人都可以访问的任何地方都会带来痛苦,无论是 exe、RAM 还是文件。
    【解决方案4】:

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

    【讨论】:

    • 如果人们决心要找到隐藏的钥匙,通常并不难。
    【解决方案5】:

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

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

    【讨论】:

    • 浪费时间。任何破解者都会直接从内存中取出函数的结果(或者可能只是将整个函数复制到他的 keygen 中)。这不是家庭作业;他只需要得到正确的答案,而不需要展示他的作品
    【解决方案6】:

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

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

    【讨论】:

    • 您的整个业务流程似乎非常落后和临时性。
    【解决方案7】:

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

    【讨论】:

    • 这种保护只比软件高一级。您可以使用任何内存转储复制 USB 密钥。如果您继续尝试通过给定的密钥 ID 检查用户在线用户,那么您也可以修补网络。没有真正的方法来保护代码。最好的方法是让试图破解它的人失望,并定期更新系统。你和能破解你的人之间的小时间框架是你唯一的朋友。
    • 让用户失望使用破解软件的最好方法是首先发布破解,并在其上放置大量病毒。这样做你可能会让比能破解你的人更重要的人失望---> 想要使用你的软件的用户。制定一个负担得起的定价计划,你会比隐藏钥匙更成功。
    【解决方案8】:

    您可以使用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
    

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

    【讨论】:

    • 这一定是个玩笑,因为在找到名为getSecretKey的入口点时,逆向组装是小菜一碟,除了LC0的密码被“隐藏”在明文中-文本。 Java 也不是用gcc 编译的,而是用javac 编译的。代码签名比你们在这里试图完成的要强大得多(基于一些妄想,这是不可能的)。
    • 指令mov esi, OFFSET FLAT:.LC0 ...已经放弃了。
    【解决方案9】:

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

    【讨论】:

    • 公钥加密是没有办法的。正如必须保护私钥的机密性一样,公钥的完整性也需要保护。常见的机制是具有物理篡改对策的硬件或基于密码的密钥。
    【解决方案10】:

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

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

    【讨论】:

      【解决方案11】:

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

      【讨论】:

        猜你喜欢
        • 2017-03-12
        • 2011-11-19
        • 2018-05-27
        • 2018-03-15
        • 2017-11-04
        • 2021-01-08
        • 2021-01-27
        • 2020-04-15
        • 2020-05-12
        相关资源
        最近更新 更多