굳이 암호화가 필요하냐? 라고 물어본다면 글쎄 필요하지 않을까, 하는 생각이고. 1차적으로 내용을 쉽게 까 볼 수 없게 숨겨야 하고, 2차적으로 단순 내용 변화에 전체 내용이 충분히 분산되어 변경되어야 하니까.
이런 방식이 제일 쉬울 것 같다.
A-Z, 0-9의 문자로 만들어진 CD-KEY를 만들려고 한다. 25글자, 그러니까 MS 사의 소프트와 같은 방식으로.. 아 계산하기 귀찮다;
알파벳 24글자에 숫자 10글자를 더하면 34글자. 그러니까 34진수로 계산하면 되는데, 25글자의 34진수를 계산하려니 머리에 쥐가 날 것 같다. 그냥 4byte씩 끊어서 계산하자. 4byte 최대, 그러니까 unsigned int 의 최대 수치를 34진수로 바꾸면 2QHXJLH가 된다. 6개의 letter를 쓰고 2가 남는건데, 16byte면 24개의 letter를 쓰고 2가 4개 남는거다. 그러니까 16byte면 34진수 25개의 letter에 손실 없이 넣을 수 있다는 말.
#pragma pack(1)
struct cd_key_data {
unsigned char MAGIC1;
unsigned int serial;
unsigned char MAGIC2;
unsigned short company;
unsigned short product;
unsigned char MAGIC3;
unsigned int crc;
unsigned char MAGIC4;
};
#pragma pack()
unsigned int 크기만큼의 물건을 unsigned short 크기만큼의 회사에서 unsigned short 크기만큼의 종류별로 팔 수 있는 구조다. 와. 벌써 부자가 된 것 같... (이건 아니고.)
오류 측정을 위해 중간중간 char 형 Magic Number를 넣어뒀다. Magic Number의 캐릭터들이 4byte 마다 들어 있는 상황은 의도된 것이다. 거기다가 crc 계산도 할꺼다. (훗!) 당연히 byte alignment는 자동으로 하게 되면 맞지 않을거니까, pragma pack로 byte alignment를 1로 맞춰두자.
/* 16byte의 Binary 데이터를 CD-KEY 형식의 25byte 문자열로 Dump 한다. 24byte를 채우고 1byte가 남게 되고, Binary 데이터쪽에서는 4bit 데이터가 2개 남는데, 이걸 마지막 1byte에 합쳐서 채운다. */
int dump_cdkey(char *buf, char *outbuf)
{
unsigned long long key1 = *((unsigned long long *)buf);
unsigned long long key2 =
*((unsigned long long *)(buf + sizeof(unsigned long long)));
int i;
for (i = 0; i < 12; i++) {
outbuf[(i << 1)] = num2key(key1 % 34);
outbuf[(i << 1) + 1] = num2key(key2 % 34);
/* 25byte 문자열로 Dump된 데이터를 다시 16byte Binary 데이터로 만든다. 만드는 방법은 생성한 순서의 역순. */
int cdkey_conv(char *buf, char *outbuf)
{
unsigned long long key1 = 0;
unsigned long long key2 = 0;
int i, letter;
for (i = 11; i >= 0; i--) {
key1 *= 34;
key2 *= 34;
key1 += key2num(*(buf + (i << 1)));
key2 += key2num(*(buf + (i << 1) + 1));
}
*((unsigned long long *)outbuf) = key1;
*((unsigned long long *)(outbuf + sizeof(unsigned long long))) = key2;
return 0;
}
/* 25Byte의 CD-KEY 문자열을 그냥 찍으면 아쉬우니까, 5byte 단위로 -를 넣어서 보기 좋게 만든다. 그냥. */
int output_cdkey(char *key)
{
int i;
for (i = 0; i < strlen(key); i++) {
if (i != 0 && (i % 5) == 0) printf("-");
printf("%c", *(key + i));
}
printf("\n");
}
/* 테스트 코드. 입력된대로 인코딩되서 CD-KEY 형식의 문자열이 나오는지, 해당 문자열이 다시 디코딩되서 입력된 데이터가 그대로 나오는지 확인한다. */
int main()
{
char buf[1024];
char outbuf[1024];
struct cd_key_data ckd;
struct cd_key_data ckd2;
unsigned int crc_save;
16byte를 한꺼번에 진법 변환을 돌리려면 귀찮으니까, unsigned long long 변수 2개로 쪼개 진법 변환을 하고, 나머지 짜투리를 한개의 letter에 쪼개서 넣었다.
CRC 계산은, 계산전에 CRC 필드를 0으로 CRC 계산을 한 후에 CRC 값을 채운다. 나중에 비교할때도 CRC 필드를 0으로 만든 다음에 CRC 계산을 해야 하는 것을 염두에 두자.
일단 암호화는 제외하고 구현해보면, 결과는 예상한대로 나올거다.
그리고 암호화를 추가해서 다시 비교해보면 최소한 그동안 뻘짓을 안했다는 뿌듯함이 생기지 않을까?
어쨌든 암호화가 빠진 코드를 시험해보자.
# ./test
MAGIC Q H X J
SERIAL 1 COMPANY 10 PRODUCT 1001 CRC 4a524ab8
BDHUJ-E314A-16N5H-1M4D4-19082
MAGIC Q H X J
SERIAL 1 COMPANY 10 PRODUCT 1001 CRC 4a524ab8:4a524ab8
#
# ./test
MAGIC Q H X J
SERIAL 2 COMPANY 10 PRODUCT 1001 CRC 4a5249b8
TTO8J-T314Q-1DN2H-1M4D4-19082
MAGIC Q H X J
SERIAL 2 COMPANY 10 PRODUCT 1001 CRC 4a5249b8:4a5249b8
#
예상대로 Serial 1과 2인 Key는 뒷부분이 완전히 동일하다. 저렇게 되면 무슨 CD-KEY라고 할 수 있겠어?
만들어놓은 TEA 3DES, 아니 어쨌든 정체불명의 암호화 코드를 적용해보자. 소스 코드는.. 다 공개되어 있잖아. 어짜피 붙여넣고 함수 하나만 불러주면 될껄. 뭐.
# ./test
MAGIC Q H X J
SERIAL 1 COMPANY 10 PRODUCT 1001 CRC 4a524ab8
BDHUJ-E314A-16N5H-1M4D4-19082
MLXFT-JOGJS-9HMS9-AI06V-1QJBR
MAGIC Q H X J
SERIAL 1 COMPANY 10 PRODUCT 1001 CRC 4a524ab8:4a524ab8
#
# ./test
MAGIC Q H X J
SERIAL 2 COMPANY 10 PRODUCT 1001 CRC 4a5249b8
TTO8J-T314Q-1DN2H-1M4D4-19082
EJ0X8-CTNJO-6SJM7-OPVPI-S3X86
MAGIC Q H X J
SERIAL 2 COMPANY 10 PRODUCT 1001 CRC 4a5249b8:4a5249b8
#
첫번째 Dump된 CD-KEY가 암호화가 적용되지 않은 버전. 두번째 Dump된 CD-KEY는 암호화가 적용된 버전이다. 보면, 뭐 그럭저럭 쓸만하게 분산되어 있다. 디코딩도 정확히 되어 입력된대로 보여준다. 좋아.