문제

C++ 애플리케이션에 중요한 정보(비공개로 유지하려는 대칭 암호화 키)를 저장해야 합니다.간단한 접근 방식은 다음과 같습니다.

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

그러나 다음을 통해 애플리케이션을 실행합니다. strings 프로세스(또는 바이너리 앱에서 문자열을 추출하는 다른 프로세스)는 위 문자열을 공개합니다.

이러한 민감한 데이터를 모호하게 하려면 어떤 기술을 사용해야 합니까?

편집하다:

좋아요, 거의 모든 분들이 말씀하셨죠 "실행 파일을 리버스 엔지니어링할 수 있습니다" - 물론!이것은 내 불만이므로 여기서 조금 불평하겠습니다.

왜 이 사이트에 있는 모든 보안 관련 질문의 99%(알겠습니다. 조금 과장했을 수도 있습니다)가 "완벽하게 안전한 프로그램을 만들 수 있는 방법은 없습니다"라는 급류로 대답하는 것입니다. 이는 도움이 되지 않습니다. 답변!보안은 완벽한 유용성과 한쪽 끝의 보안 없음, 완벽한 보안이지만 다른 쪽 끝의 유용성 사이의 슬라이딩 스케일입니다.

요점은 수행하려는 작업과 소프트웨어가 실행될 환경에 따라 차등 규모에서 위치를 선택한다는 것입니다. 저는 군사 시설용 앱을 작성하는 것이 아니라 가정용 PC용 앱을 작성하고 있습니다..미리 알려진 암호화 키를 사용하여 신뢰할 수 없는 네트워크에서 데이터를 암호화해야 합니다.이러한 경우에는 "모호함을 통한 보안"으로 충분할 것입니다!물론 충분한 시간과 에너지, 기술을 갖춘 사람이라면 바이너리를 리버스 엔지니어링하여 비밀번호를 찾을 수 있겠지만, 어떨까요?난 상관없어:

최고 수준의 보안 시스템을 구현하는 데 걸리는 시간은 크랙된 버전으로 인한 매출 손실보다 더 비쌉니다(실제로 이것을 판매한다는 뜻은 아니지만 제 말이 이해가 되실 겁니다).새로운 프로그래머들 사이에서 프로그래밍에 있어 "가능한 최선의 방법으로 해보자"라는 푸른 하늘의 경향은 아무리 말해도 어리석습니다.

시간을 내어 이 질문에 답변해 주셔서 감사합니다. 이 질문이 가장 도움이 되었습니다.안타깝게도 답변은 하나만 받아들일 수 있지만 유용한 답변은 모두 찬성 투표했습니다.

도움이 되었습니까?

해결책

기본적으로 프로그램에 액세스 할 수있는 사람과 디버거 ~할 수 있다 그리고 ~ 할 것이다 원하는 경우 응용 프로그램에서 키를 찾으십시오.

그러나 실행 중에 키가 나타나지 않도록하려면 strings 이진에서는 예를 들어 키가 인쇄 범위 내에 있지 않은지 확인할 수 있습니다.

XOR로 키를 가리 킵니다

예를 들어 XOR을 사용하여 키를 2 바이트 어레이로 분할 할 수 있습니다.

key = key1 XOR key2

바이트 길이와 같은 KEY1을 생성하는 경우 key (완전히) 임의 바이트 값을 사용한 다음 계산할 수 있습니다. key2:

key1[n] = crypto_grade_random_number(0..255)
key2[n] = key[n] XOR key1[n]

빌드 환경 에서이 작업을 수행 한 다음 저장할 수 있습니다. key1그리고 key2 신청서에서.

이진 보호

또 다른 방법은 이진을 보호하기위한 도구를 사용하는 것입니다. 예를 들어, 이진이 난독 화되고 실행되는 가상 시스템을 시작할 수있는 몇 가지 보안 도구가 있습니다. 이로 인해 디버그하기가 어렵고 많은 상업적 등급 보안 응용 프로그램 (Alas, Amware)이 보호되는 수문이기도합니다.

최고의 도구 중 하나는입니다 Themida, 그것은 당신의 바이너리를 보호하는 멋진 일을합니다. Spotify와 같은 잘 알려진 프로그램에서 역 엔지니어링으로부터 보호하기 위해 종종 사용됩니다. OLLYDBG 및 IDA Pro와 같은 프로그램의 디버깅을 방지하는 기능이 있습니다.

또한 더 큰 목록, 아마도 다소 오래된 목록이 있습니다. 이진을 보호하는 도구.
그들 중 일부는 무료입니다.

비밀번호 일치

여기 누군가가 해싱 비밀번호+소금에 대해 논의했습니다.

어떤 종류의 사용자 제출 비밀번호와 일치하도록 키를 저장 해야하는 경우, 사용자 이름, 비밀번호 및 소금을 결합하여 일방 통행 해싱 기능을 사용해야합니다. 그러나 이것의 문제점은 응용 프로그램이 일방 통행을 수행하고 결과 해시를 비교할 수 있도록 소금을 알아야한다는 것입니다. 따라서 응용 프로그램 어딘가에 소금을 보관해야합니다. 그러나 @edward가 아래 의견에서 지적한 것처럼, 이것은 예를 들어 레인보우 테이블을 사용하여 사전 공격으로 효과적으로 보호합니다.

마지막으로 위의 모든 기술의 조합을 사용할 수 있습니다.

다른 팁

우선, 충분히 결정된 해커를 막을 수있는 일이없고 주변에 많은 사람들이 있습니다. 모든 게임과 콘솔의 보호는 결국 금이되므로 임시 수정 일뿐입니다.

당신이 할 수있는 4 가지가 있습니다.

1) 어떤 식 으로든 문자열의 요소를 숨기십시오 - Xoring ( ^ 연산자)과 같은 명백한 것 다른 문자열이있는 문자열은 문자열을 검색 할 수 없게 만들기에 충분합니다.

2) 문자열을 조각으로 나눕니다. 스트링과 팝 비트를 이상하게 명명 된 메소드로 쪼개십시오. 문자열이있는 방법을 쉽게 검색하고 메소드를 찾을 수 없습니다. 물론 일부 방법은이 모든 비트를 호출해야하지만 여전히 조금 더 어려워집니다.

3) 메모리에 문자열을 빌드하지 마십시오. 대부분의 해커는 인코딩 된 후에 문자열을 메모리에서 볼 수있는 도구를 사용합니다. 가능하다면 이것을 피하십시오. 예를 들어 키를 서버로 보내는 경우 문자로 문자를 보내십시오. 그래서 전체 문자열이 주변에 없습니다. 물론 RSA 인코딩과 같은 제품에서 사용하는 경우 더 까다 롭습니다.

4) 임시 알고리즘을 수행하십시오.이 무엇보다도 독특한 트위스트를 추가하십시오. 어쩌면 생산하는 모든 것에 1을 추가하거나 암호화를 두 번 또는 설탕을 첨가 할 수도 있습니다. 이것은 누군가가 바닐라 MD5 해싱 또는 RSA 암호화와 같은 누군가를 사용할 때 무엇을 찾아야하는지 이미 알고있는 해커에게는 조금 더 어려워집니다.

무엇보다도, 키가 발견 될 때 (그리고 응용 프로그램이 충분히 인기를 얻을 때)가 너무 중요하지 않은지 확인하십시오!

내가 과거에 사용한 전략은 겉보기에 랜덤 캐릭터 배열을 만드는 것입니다. 처음에는 삽입 한 다음 각 단계에서 0에서 n의 각 단계가 난독 화 된 문자열에 다음 숯이 포함 된 배열의 숫자 <크기를 생성하는 대수 프로세스로 특정 문자를 찾습니다. (이 대답은 지금 난독 화 된 느낌입니다!)

예시:

숯불 배열이 주어지면 (숫자와 대시는 참조 용입니다)

0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL

처음 6 개의 결과가 다음과 같은 방정식 : 3, 6, 7, 10, 21, 47

"안녕하세요!"라는 단어를 양보 할 것입니다. 위의 배열에서.

@checkers에 동의합니다. 실행 파일을 리버스 엔지니어링 할 수 있습니다.

조금 더 좋은 방법은 예를 들어 동적으로 만드는 것입니다.

std::string myKey = part1() + part2() + ... + partN();

문자열 용 간단한 암호화 도구를 만들었고 암호화 된 문자열을 자동으로 생성 할 수 있으며 몇 가지 추가 옵션이 있습니다.

글로벌 변수로 문자열 :

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xCF, 0x34, 0xF8, 0x5F, 0x5C, 0x3D, 0x22, 0x13, 0xB4, 0xF3, 0x63, 0x7E, 0x6B, 0x34, 0x01, 0xB7, 0xDB, 0x89, 0x9A, 0xB5, 0x1B, 0x22, 0xD4, 0x29, 0xE6, 0x7C, 0x43, 0x0B, 0x27, 0x00, 0x91, 0x5F, 0x14, 0x39, 0xED, 0x74, 0x7D, 0x4B, 0x22, 0x04, 0x48, 0x49, 0xF1, 0x88, 0xBE, 0x29, 0x1F, 0x27 };

myKey[30] -= 0x18;
myKey[39] -= 0x8E;
myKey[3] += 0x16;
myKey[1] += 0x45;
myKey[0] ^= 0xA2;
myKey[24] += 0x8C;
myKey[44] ^= 0xDB;
myKey[15] ^= 0xC5;
myKey[7] += 0x60;
myKey[27] ^= 0x63;
myKey[37] += 0x23;
myKey[2] ^= 0x8B;
myKey[25] ^= 0x18;
myKey[12] ^= 0x18;
myKey[14] ^= 0x62;
myKey[11] ^= 0x0C;
myKey[13] += 0x31;
myKey[6] -= 0xB0;
myKey[22] ^= 0xA3;
myKey[43] += 0xED;
myKey[29] -= 0x8C;
myKey[38] ^= 0x47;
myKey[19] -= 0x54;
myKey[33] -= 0xC2;
myKey[40] += 0x1D;
myKey[20] -= 0xA8;
myKey[34] ^= 0x84;
myKey[8] += 0xC1;
myKey[28] -= 0xC6;
myKey[18] -= 0x2A;
myKey[17] -= 0x15;
myKey[4] ^= 0x2C;
myKey[9] -= 0x83;
myKey[26] += 0x31;
myKey[10] ^= 0x06;
myKey[16] += 0x8A;
myKey[42] += 0x76;
myKey[5] ^= 0x58;
myKey[23] ^= 0x46;
myKey[32] += 0x61;
myKey[41] ^= 0x3B;
myKey[31] ^= 0x30;
myKey[46] ^= 0x6C;
myKey[35] -= 0x08;
myKey[36] ^= 0x11;
myKey[45] -= 0xB6;
myKey[21] += 0x51;
myKey[47] += 0xD9;

암호 해독 루프가있는 유니 코드 문자열 :

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
wchar_t myKey[48];

myKey[21] = 0x00A6;
myKey[10] = 0x00B0;
myKey[29] = 0x00A1;
myKey[22] = 0x00A2;
myKey[19] = 0x00B4;
myKey[33] = 0x00A2;
myKey[0] = 0x00B8;
myKey[32] = 0x00A0;
myKey[16] = 0x00B0;
myKey[40] = 0x00B0;
myKey[4] = 0x00A5;
myKey[26] = 0x00A1;
myKey[18] = 0x00A5;
myKey[17] = 0x00A1;
myKey[8] = 0x00A0;
myKey[36] = 0x00B9;
myKey[34] = 0x00BC;
myKey[44] = 0x00B0;
myKey[30] = 0x00AC;
myKey[23] = 0x00BA;
myKey[35] = 0x00B9;
myKey[25] = 0x00B1;
myKey[6] = 0x00A7;
myKey[27] = 0x00BD;
myKey[45] = 0x00A6;
myKey[3] = 0x00A0;
myKey[28] = 0x00B4;
myKey[14] = 0x00B6;
myKey[7] = 0x00A6;
myKey[11] = 0x00A7;
myKey[13] = 0x00B0;
myKey[39] = 0x00A3;
myKey[9] = 0x00A5;
myKey[2] = 0x00A6;
myKey[24] = 0x00A7;
myKey[46] = 0x00A6;
myKey[43] = 0x00A0;
myKey[37] = 0x00BB;
myKey[41] = 0x00A7;
myKey[15] = 0x00A7;
myKey[31] = 0x00BA;
myKey[1] = 0x00AC;
myKey[47] = 0x00D5;
myKey[20] = 0x00A6;
myKey[5] = 0x00B0;
myKey[38] = 0x00B0;
myKey[42] = 0x00B2;
myKey[12] = 0x00A6;

for (unsigned int fngdouk = 0; fngdouk < 48; fngdouk++) myKey[fngdouk] ^= 0x00D5;

글로벌 변수로 문자열 :

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xAF, 0xBB, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xA7, 0xA5, 0xB4, 0xA7, 0xB6, 0xB2, 0xA3, 0xB5, 0xB5, 0xB9, 0xB1, 0xB4, 0xA6, 0xB6, 0xAA, 0xA3, 0xB6, 0xBB, 0xB1, 0xB7, 0xB9, 0xAB, 0xAE, 0xAE, 0xB0, 0xA7, 0xB8, 0xA7, 0xB4, 0xA9, 0xB7, 0xA7, 0xB5, 0xB5, 0x42 };

for (unsigned int dzxykdo = 0; dzxykdo < 48; dzxykdo++) myKey[dzxykdo] -= 0x42;

물론 사용자에게 배송되는 소프트웨어에 개인 데이터를 저장하는 것은 항상 위험입니다. 충분히 교육받은 (그리고 전용) 엔지니어는 데이터를 리버스 엔지니어링 할 수 있습니다.

즉, 사람들이 개인 데이터를 공개하기 위해 극복 해야하는 장벽을 높이면 종종 충분히 안전하게 만들 수 있습니다. 그것은 일반적으로 좋은 타협입니다.

귀하의 경우, 인쇄 할 수없는 데이터로 문자열을 혼란시킨 다음 간단한 도우미 기능을 사용하여 런타임을 해독 할 수 있습니다.

void unscramble( char *s )
{
    for ( char *str = s + 1; *str != 0; str += 2 ) {
        *s++ = *str;
    }
    *s = '\0';
}

void f()
{
    char privateStr[] = "\001H\002e\003l\004l\005o";
    unscramble( privateStr ); // privateStr is 'Hello' now.

    string s = privateStr;
    // ...
}

Joshperry가 지적한 것처럼 보호하려는 내용에 다소 의존합니다. 경험을 통해 소프트웨어를 보호하기위한 라이센스 체계의 일부라면 귀찮게하지 않는다고 말합니다. 그들은 그것을 반대 엔지니어링 할 것입니다. ROT-13과 같은 간단한 암호를 사용하여 간단한 공격으로부터 보호하십시오 (라인 실행 문자열). 사용자의 민감한 데이터를 보호하는 경우 로컬로 저장된 개인 키로 해당 데이터를 보호하는 것이 현명한 움직임인지 의문을 제기 할 것입니다. 다시 한 번 그것은 당신이 보호하려는 것에 달려 있습니다.

편집 : 만약 당신이 그것을하려고한다면 Chris가 지적하는 기술의 조합은 ROT13보다 훨씬 나아질 것입니다.

이전에 말했듯이, 당신의 줄을 완전히 보호 할 방법이 없습니다. 그러나 합리적인 안전을 지키는 방법이 있습니다.

이 작업을 수행해야했을 때, 나는 무고한 외모의 문자열을 코드에 넣었습니다 (예 : 저작권 통지 또는 가짜 사용자 프롬프트 또는 관련없는 코드를 수정하지 않은 사람이 변경하지 않는 다른 사람)는 자체를 사용하여 암호화했습니다. 열쇠로, 해시 (소금 추가)를 해시하고 결과를 실제로 암호화하고 싶은 것을 암호화하기위한 열쇠로 사용했습니다.

물론 이것은 해킹 될 수 있지만 결정된 해커가 필요합니다.

실행 파일에 개인 키를 저장하는 대신 사용자에게 요청하고 외부를 통해 저장할 수 있습니다. 비밀번호 관리자, Mac OS X 키 체인 액세스와 유사한 것.

Windows 사용자 DPAPI에있는 경우 http://msdn.microsoft.com/en-us/library/ms995355.aspx

이전 게시물에서 말했듯이 Mac에있는 경우 Keychain을 사용하십시오.

기본적으로 개인 키를 바이너리에 보관하는 방법에 대한 이러한 귀여운 아이디어는 모두 보안 관점에서 충분히 열악합니다. 개인 키를 얻는 사람은 누구나 큰 문제입니다. 프로그램 내부에 보관하지 마십시오. 앱 가져 오기 방법에 따라 개인 키를 스마트 카드에 보관할 수 있습니다. 원격 컴퓨터에서 코드가 대화하는 원격 컴퓨터에서 대부분의 사람들이하는 일을 수행하고 로컬 컴퓨터 ( "키"가있는 매우 안전한 장소에 보관할 수 있습니다. 권한과 OS의 모든 강점에 의해 보호되는 "이상한 보안 레지스트리와 같은 종류).

이것은 해결 된 문제이며 대답은 프로그램 내부에서 열쇠를 유지하는 것이 아닙니다. :)

노력하다 이것. 소스 코드는 주어진 Visual Studio C ++ 프로젝트의 모든 문자열을 즉시 암호화하고 해독하는 방법을 설명합니다.

최근에 시도한 한 가지 방법은 다음과 같습니다.

  1. 개인 데이터의 해시 (SHA256)를 가져 와서 코드로 채우십시오. part1
  2. 개인 데이터와 해시의 XOR을 가져 와서 코드로 채우십시오. part2
  3. 채집 : 데이터 : char str []로 저장하지 말고 할당 지침을 사용하여 런타임에 채워집니다 (아래 매크로에 표시).
  4. 이제 XOR을 사용하여 실행 시간에 대한 개인 데이터를 생성합니다. part1 그리고 part2
  5. 추가 단계: 생성 된 데이터의 해시를 계산하고 비교 part1. 개인 데이터의 무결성을 확인합니다.

데이터를 채우는 매크로 :

개인 데이터는 4 바이트입니다. 임의의 순서로 할당 지침이있는 데이터를 저장하는 매크로를 정의합니다.

#define POPULATE_DATA(str, i0, i1, i2, i3)\
{\
    char *p = str;\
    p[3] = i3;\
    p[2] = i2;\
    p[0] = i0;\
    p[1] = i1;\
}

이제 저장 해야하는 코드 에서이 매크로를 사용하십시오. part1 그리고 part2, 다음과 같이 :

char part1[4] = {0};
char part2[4] = {0};
POPULATE_DATA(part1, 1, 2, 3, 4); 
POPULATE_DATA(part2, 5, 6, 7, 8);

상황에 따라 다르지만 그냥 저장할 수도 있습니다. 해시시 키에 a를 더한 것 소금 (상수 문자열, 모호해지기 쉬움)

그런 다음 (if) 사용자가 키를 입력하면 소금, 계산하다 해시시 그리고 비교해 보세요.

그만큼 소금 이 경우에는 불필요할 수 있습니다. 해시를 격리할 수 있으면 무차별 사전 공격을 중지합니다(Google 검색도 작동하는 것으로 알려져 있습니다).

해커는 전체를 우회하기 위해 어딘가에 jmp 명령을 삽입하기만 하면 되지만 이는 단순한 텍스트 검색보다 다소 복잡합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top