C ++에서 민감한 문자열을 가리는 기술


87

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

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

그러나 strings프로세스 (또는 바이너리 앱에서 문자열을 추출하는 다른 모든 것)를 통해 애플리케이션을 실행 하면 위의 문자열이 표시됩니다.

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

편집하다:

좋습니다. 거의 모든 사람들이 "실행 파일을 리버스 엔지니어링 할 수 있습니다" 라고 말했습니다 . 물론입니다! 이건 내 반려견 이니까 여기에서 약간 고함을 쳐 보겠습니다.

이 사이트에있는 모든 보안 관련 질문의 99 % (예, 약간 과장)가 "완벽하게 안전한 프로그램을 만들 수있는 방법이 없습니다"라는 급류로 답변되는 이유는 무엇입니까? 대답! 보안은 완벽한 사용 성과 한쪽 끝의 보안 없음, 그리고 다른 쪽 끝에서는 완벽한 보안이지만 사용성은없는 슬라이딩 스케일입니다.

요점은 수행하려는 작업과 소프트웨어가 실행되는 환경에 따라 슬라이딩 스케일에서 위치를 선택한다는 것입니다. 나는 군사 시설용 앱을 작성하는 것이 아니라 가정용 PC 용 앱을 작성하고 있습니다 . 미리 알려진 암호화 키를 사용하여 신뢰할 수없는 네트워크에서 데이터를 암호화해야합니다. 이 경우 "모호함을 통한 보안"으로 충분할 것입니다! 물론, 충분한 시간, 에너지 및 기술을 가진 사람은 바이너리를 리버스 엔지니어링하고 암호를 찾을 수 있지만 무엇을 추측합니까? 상관 없음 :

최고 수준의 보안 시스템을 구현하는 데 걸리는 시간은 금이 간 버전으로 인한 판매 손실보다 더 비쌉니다 (실제로이 제품을 판매하는 것은 아니지만 내 요점을 알 수 있음). 이 푸른 하늘은 새로운 프로그래머들 사이에서 프로그래밍에서 "가능한 절대적인 최선의 방법으로 그렇게한다"는 것은 어리석은 말입니다.

이 질문에 답 해주셔서 감사합니다. 가장 도움이되었습니다. 안타깝게도 한 가지 답변 만받을 수 있지만 유용한 답변을 모두 찬성했습니다.


2
암호화 키를 사용하여 수행하려는 작업을 설명하는 경우이를 수행 할 필요가 없도록하는 방법에 대한 조언을 제공 할 수 있습니다.
joshperry


4
@Kirill :이 질문을 당신이 언급 한 질문과 똑같이 부르기는 어렵습니다. 실제로 아이디어는 동일합니다. 문제는 아닙니다.
xtofl

1
@xtofl, 투표하지 않아도됩니다. 나에게 그것은 두 개의 동일한 질문처럼 보입니다.
Kirill V. Lyadvinsky

2
(폭언과 무관) "비공개 키"의 정의 자체는 여러분이 제공하지 않는 공개 / 개인 키 쌍의 절반입니다. 이 경우에도 개인 키는 서버에 보관하는 키입니다. 클라이언트 앱에있는 것은 공개입니다.
MSalters

답변:


42

기본적으로, 프로그램에 대한 접근 및 디버거와 사람 그들이 원하는 경우 응용 프로그램에서 키를 찾을 수 있습니다.

그러나 strings바이너리에서 실행할 때 키가 표시되지 않도록 하려면 예를 들어 키가 인쇄 가능한 범위 내에 있지 않은지 확인할 수 있습니다.

XOR로 키 가리기

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

key = key1 XOR key2

key(완전히) 임의의 바이트 값을 사용할 수 있는 것과 동일한 바이트 길이로 key1을 만든 다음 다음을 계산합니다 key2.

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

빌드 환경에서이 작업을 수행 한 다음 애플리케이션 에만 저장 key1하고 저장할 key2수 있습니다.

바이너리 보호

또 다른 접근 방식은 도구를 사용하여 바이너리를 보호하는 것입니다. 예를 들어 바이너리가 난독 화되었는지 확인하고 실행되는 가상 머신을 시작할 수있는 몇 가지 보안 도구가 있습니다. 이로 인해 디버그하기가 어렵고 많은 상용 등급 보안 애플리케이션 (아마도 멀웨어)을 보호 할 수있는 중요한 방법이기도합니다.

최고의 도구 중 하나는 Themida입니다 .이 도구는 바이너리를 보호하는 훌륭한 역할을합니다. 리버스 엔지니어링으로부터 보호하기 위해 Spotify와 같은 잘 알려진 프로그램에서 자주 사용됩니다. OllyDbg 및 Ida Pro와 같은 프로그램에서 디버깅을 방지하는 기능이 있습니다.

바이너리를 보호하기 위한 더 큰 목록, 아마도 다소 구식 인 도구 목록도 있습니다 .
그들 중 일부는 무료입니다.

비밀번호 일치

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

사용자가 제출 한 암호와 일치하도록 키를 저장해야하는 경우 사용자 이름, 암호 및 솔트를 결합하여 일방향 해싱 기능을 사용하는 것이 좋습니다. 그러나 이것의 문제는 단방향을 수행하고 결과 해시를 비교할 수 있도록 애플리케이션이 솔트를 알아야한다는 것입니다. 따라서 애플리케이션 어딘가에 소금을 저장해야합니다. 그러나 @Edward가 아래 주석에서 지적했듯이 이것은 레인보우 테이블과 같은 사전 공격으로부터 효과적으로 보호합니다.

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


사용자가 입력해야하는 암호 인 경우 암호의 해시 + 솔트를 저장합니다. 아래 답변을 참조하십시오.

1
@hapalibashi : 애플리케이션에 소금을 안전하게 저장하는 방법은 무엇입니까? 나는 OP에 단방향 암호 일치 시스템이 필요하지 않고 정적 키를 저장하는 일반화 된 방법이 필요하다고 생각합니다.
csl

2
디스 어셈블 된 프로그램을 볼 때 일반적으로 XOR이 많지 않다는 것을 발견했습니다. 따라서 XOR을 사용하여 무언가를 모호하게하려는 경우에는 자신에게주의를 기울이는 것을 염두에 두십시오.
kb.

2
@kb-흥미로운 점입니다. xor보다 훨씬 더 많이 발생하는 비트 및 ors를 볼 수있을 것입니다. a ^ b == (a & ~ b) || (~ a & b)
Jeremy Powell

4
솔트 값을 아는 것은 일반적으로 적에게 이점을 제공하지 않습니다. 솔트의 요점은 공격자가 많은 가능성있는 입력에 대해 해시를 미리 계산 한 "사전 공격"을 피하는 것입니다. 솔트를 사용하면 솔트를 기반으로 한 새 사전으로 사전 계산을해야합니다. 각 소금을 한 번만 사용하면 사전 공격이 완전히 쓸모 없게됩니다.
Edward Dixon

8

우선, 충분히 단호한 해커를 막기 위해 할 수있는 일이 없으며 주변에 해커가 많이 있다는 것을 인식하십시오. 주변의 모든 게임과 콘솔에 대한 보호 기능이 결국 깨져서 일시적인 수정일뿐입니다.

잠시 동안 숨어있을 가능성을 높이기 위해 할 수있는 4 가지 방법이 있습니다.

1) 어떤 식 으로든 문자열의 요소를 숨 깁니다. 즉, 다른 문자열로 문자열을 xoring (^ 연산자)하는 것과 같이 분명한 것은 문자열을 검색 할 수 없게 만드는 데 충분합니다.

2) 문자열을 조각으로 나누십시오-문자열을 나누고 이상한 모듈에서 이상한 이름의 메소드로 비트를 팝하십시오. 문자열이 포함 된 메서드를 쉽게 검색하고 찾을 수 있도록 만들지 마십시오. 물론 일부 메서드는 이러한 모든 비트를 호출해야하지만 여전히 조금 더 어렵게 만듭니다.

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

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

무엇보다도 키가 발견 될 때 (애플리케이션이 충분히 인기를 얻게되는 경우) 너무 중요하지 않은지 확인하십시오!


5

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

예:

문자 배열이 주어짐 (숫자와 대시는 참조 용임)

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

그리고 처음 6 개의 결과가 3, 6, 7, 10, 21, 47 인 방정식

"HELLO!"라는 단어가 나올 것입니다. 위의 배열에서.


좋은 생각 - 당신이 더 ... 배열의 비 인쇄 문자를 사용하여 개선 할 수있는 것 같아요
Thomi

4

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

좀 더 나은 방법은 동적으로 생성하는 것입니다. 예를 들면 다음과 같습니다.

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

사실, 바이너리를 검색 할 때 문자열이 드러나지 않도록합니다. 그러나, 당신의 문자열은 여전히 ​​메모리에 상주합니다. 당신의 솔루션은 아마도 내가하는 일에 대해 충분할 것입니다.
Thomi

@Thomi, 물론 작업을 마치 자마자 파괴 할 수 있습니다 . 그러나 여전히 민감한 문자열 을 처리하는 가장 좋은 방법은 아닙니다 .
Nick Dandoulakis

... 파괴하기 때문에 실제로 메모리가 즉시 재사용된다는 보장은 없습니다.
Thomi

4

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

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

귀하의 경우에는 인쇄 할 수없는 데이터로 문자열을 어수선하게 만든 다음 다음과 같은 간단한 도우미 함수를 사용하여 런타임에 디코딩 할 수 있습니다.

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;
    // ...
}

4

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

전역 변수로서의 문자열 :

// 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;

아니, 나는 일을하기 위해 stringencrypt.com 웹 사이트를 사용 했다. 간단한 문자열 암호화를 자동화하는 데 사용할 수있는 C / C ++ stringencrypt.com/c-cpp-encryption에 대한 예제 가 있습니다.
Bartosz Wójcik 2013

2

joshperry가 지적한 것처럼 보호하려는 대상에 다소 의존합니다. 경험상 소프트웨어를 보호하기위한 라이선싱 체계의 일부라면 신경 쓰지 마십시오. 그들은 결국 그것을 역 설계 할 것입니다. ROT-13과 같은 간단한 암호를 사용하여 간단한 공격 (줄이 흐르는 문자열)으로부터 보호하십시오. 사용자의 민감한 데이터를 보호하려면 로컬에 저장된 개인 키로 해당 데이터를 보호하는 것이 현명한 조치인지 의문이들 것입니다. 다시 한 번 당신이 보호하려는 것에 달려 있습니다.

편집 : 당신이 그것을 할 경우 Chris가 지적한 기술 조합이 rot13보다 훨씬 낫습니다.


2

이전에 말했듯이 문자열을 완전히 보호 할 방법은 없습니다. 그러나 합리적인 안전을 위해 그것을 보호하는 방법이 있습니다.

이 작업을 수행해야 할 때 코드에 순진한 문자열 (예 : 저작권 고지, 가짜 사용자 프롬프트 또는 관련없는 코드를 수정하는 누군가가 변경하지 않는 기타 모든 것)을 삽입하고 자체적으로 암호화했습니다. 키로 해시하고 (솔트 추가) 결과를 키로 사용하여 실제로 암호화하려는 것을 암호화했습니다.

물론 이것은 해킹 될 수 있지만 그렇게하려면 단호한 해커가 필요합니다.


좋은 생각-또 다른 형태의 모호함은 여전히 ​​상당히 강하지 만 (긴 문장 부호 및 모든 재즈) 확실히 암호처럼 보이지 않는 문자열을 사용하는 것입니다.
Thomi

1

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

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

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

이것은 해결 된 문제이며 답은 프로그램 내부에 키를 보관하지 않는 것입니다. :)



1

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

  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);

1

A (매우 가벼운) 헤더 전용 프로젝트가 난독 완벽하게 작동 adamyaxley에 의해 만들어진. 람다 함수 및 매크로를 기반으로하며 컴파일 타임에 XOR 암호로 문자열을 암호화합니다. 필요한 경우 각 문자열의 시드를 변경할 수 있습니다.

다음 코드는 컴파일 된 바이너리에 "hello world"문자열을 저장하지 않습니다.

#include "obfuscate.h"

int main()
{
  std::cout << AY_OBFUSCATE("Hello World") << std::endl;
  return 0;
}

C ++ 17 및 Visual Studio 2019로 테스트했으며 IDA를 통해 확인하고 문자열이 숨겨져 있는지 확인합니다. ADVobfuscator에 비해 한 가지 귀중한 이점 은 std :: string으로 변환 할 수 있다는 것입니다 (컴파일 된 바이너리에 여전히 숨겨져 있음).

std::string var = AY_OBFUSCATE("string");

0

실행 파일에 개인 키를 저장하는 대신 사용자에게 요청 하여 Mac OS X 키 체인 접근과 유사한 외부 암호 관리자 를 통해 저장할 수 있습니다.


예, 아니오. 일반적으로 동의합니다.하지만이 경우에는 소프트웨어 사용자에게 이것을 숨기려고하므로 외부 시스템에 저장하는 것은 좋은 생각이 아닙니다 (많은 키 체인 시스템이 적절한 권한이 부여 된 사용자에게 암호를 일반 텍스트로 노출). 키 체인 소프트웨어는 사용자 암호에 적합하지만 애플리케이션 암호화 키에는 적합하지 않습니다.
Thomi

그것이 안전하지 않은 느낌 경우 (당신의 해명을 부여하지만 어쩌면이 적절한) 당신은 : 하드 코딩 키 체인 뭔가 결합 할 수

0

컨텍스트에 따라 다르지만 키 의 해시솔트 (상수 문자열, 모호하기 쉬운) 만 저장할 수 있습니다 .

그런 다음 사용자가 키를 입력하면 솔트 를 추가 하고 해시를 계산 하고 비교합니다.

이 경우 솔트 는 아마도 불필요 할 것입니다. 해시를 분리 할 수 ​​있다면 무차별 대입 사전 공격을 중지합니다 (Google 검색도 작동하는 것으로 알고 있음).

해커는 여전히 모든 것을 우회하기 위해 어딘가에 jmp 명령을 삽입해야하지만 단순한 텍스트 검색보다 다소 복잡합니다.


이것은 암호 해시가 아니라 암호화 키입니다. 데이터를 인코딩 및 디코딩하려면 실제 키가 필요합니다. 사용자는 키를 볼 수 없으며 바이너리 외부에 저장되지 않습니다.
Thomi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.