오피스 문서의 구조를 볼 수 있는 Structured Storage Viewer로 문서 파일을 열어보면, EncryptionInfo에서 암호화 정보를 XML로 확인할 수 있습니다. 제 문서의 경우는 아래와 같았습니다.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<encryption xmlns="http://schemas.microsoft.com/office/2006/encryption"
xmlns:p="http://schemas.microsoft.com/office/2006/keyEncryptor/password"
xmlns:c="http://schemas.microsoft.com/office/2006/keyEncryptor/certificate">
<keyData
saltSize="16"
blockSize="16"
keyBits="256"
hashSize="64"
cipherAlgorithm="AES"
cipherChaining="ChainingModeCBC"
hashAlgorithm="SHA512"
saltValue="KtwuzFCcK6QP3ysDp0c9VA=="
/>
<dataIntegrity
encryptedHmacKey="huc0j3uogWrKgic0aUl1dcGY+ESN0MhIH59PX+uBVwFr5qlqthL/StMxY4Lmt+WmBB3I1eAM1G1kinPDnwshyg=="
encryptedHmacValue="uNQ9sZRZglMpRUFmsymsm7QCTC0IJy0PD6Pw+9ymrAAdJN+Lz1DuJlHyCuMr9L9appVn4Iy8QbDPG6WyTVoVyQ=="
/>
<keyEncryptors>
<keyEncryptor uri="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
<p:encryptedKey
spinCount="100000"
saltSize="16"
blockSize="16"
keyBits="256"
hashSize="64"
cipherAlgorithm="AES"
cipherChaining="ChainingModeCBC"
hashAlgorithm="SHA512"
saltValue="IdxU1HZJTfqRJ2lxI0JL2A=="
encryptedVerifierHashInput="+VL6xUT+JSrrgz9TZzM0uw=="
encryptedVerifierHashValue="2nEZi/gDl/2/K6Bp77YQfgHVmzucUdpK6vcv8vnPZcaZPrdtgx84AsbpqTLs8AMDNmB4QvCj7Iww+o0cmG+G7g=="
encryptedKeyValue="rRU8RZoSDlX9ZYGx+bJsCVh8WVeSmbAzttbNKzuT27U="
/>
</keyEncryptor>
</keyEncryptors>
</encryption>
[키 생성]
[키 검증]
첫번째 그림은 키 생성부입니다. 2개의 대칭키(복호화 키)를 뽑아내고 있습니다. 1 ~ 5)번까지의 과정입니다. 두번째 그림은 키 검증부입니다. 6 ~ 8)번까지의 과정입니다.
1) 16바이트 salt와 사용자 입력 패스워드를 sha512 해시(이하 해시)합니다.
2) 현재의 spin count를 입력하고 1)에서 생성한 해시를 붙입니다. (spin count (4바이트) + hash (64바이트))
3) 2)를 해시하고 spin count 뒤에 붙입니다. 이를 10만번 반복합니다. 최종적으로 패스워드 해시가 생성됩니다.
4) hash input 블록키를 패스워드 해시 뒤에 붙이고 해시합니다. 해당 해시는 encryptedVerifierHashInput의 대칭키가 됩니다.
5) hash value 블록키를 패스워드 해시 뒤에 붙이고 해시합니다. encryptedVerifierHashValue의 대칭키가 됩니다.
이제 얻어낸 키로 암호화 된 2개의 데이터를 복호화 해야 합니다.
AES 256비트와 CBC모드를 사용중이며 IV는 32바이트인데 salt (16바이트) + 패딩 0 (16바이트)로 되어 있습니다.
6) 4, 5)에서 얻어낸 각각의 대칭키를 넣고 복호화를 하여 16바이트 / 64바이트 데이터를 얻습니다.
7) 복호화 된 hash input 데이터를 해시한 후 복호화 된 hash value와 비교합니다.
8) 일치하면 올바른 패스워드입니다.
3) IV연산은 16바이트 salt 에 4바이트 블록키를 더해서 생성합니다. 디폴트 블록키는 0이며 한 사이클당 1씩 증가합니다.
4) salt+블록키 = 20바이트를 해시합니다.
#include "stdafx.h"
#include <Windows.h>
#include <openssl/sha.h>
#include <openssl/aes.h>
#include "base64.h"
// 사용자 입력 패스워드 (unicode)
#define Password L"1234"
/* Input XML */
// From KeyData
#define SaltValue "PyRz/rrl5W3f8APOTTRqEg=="
// From KeyEncryptor
#define Salt "I9I1XvYqB2CZ/Xj/Ktt8tg=="
#define EncryptedVerifierHashInput "CoY5oPNs2r0eNXgnGxpVSA=="
#define EncryptedVerifierHashValue "oDHZqZdAG/ues/73uvAOL4yG2HlILGxIO2E0kfJ/QWFqO1E9JuX8jBvtR93o26DR0UimnRRI5NjarERGk4ajfA=="
#define EncryptedKeyValue "TblBTCEtWuGcM9qdLMMCkGNhJXiTNUcgg486ykUr7KA="
#define SPIN_COUNT 100000
#define MAX_FILE_SIZE 999999
void Decrypt2(
IN PBYTE pbySalt, // salt
IN PBYTE pbyKey, // 복호화용 대칭키
IN PBYTE pbyEncryptedContent, // 암호화 된 컨텐츠
OUT PBYTE pbyDecryptedContent, // 복호화 된 컨텐츠
IN int cbDecryptLength // 복호화 된 문서 길이
)
{
DWORD dwBlockSize = 4096; // 블록 길이
DWORD dwRead = 0; // 총 복호화 한 길이
int n = (cbDecryptLength + dwBlockSize - 1) / dwBlockSize; // 복호화 횟수
for (int i = 0; i < n; i++)
{
DWORD dwLen = 0;
// 한 블록당 복호화 할 길이를 구한다.
if (i < n - 1)
{
dwLen = dwBlockSize;
}
else
{
dwLen = cbDecryptLength % dwBlockSize;
}
// iv + blockkey
BYTE temp[20] = { 0, };
// 블록키는 1씩 증가한다.
BYTE byBlockKey[4] = { 0, };
memcpy(byBlockKey, &i, sizeof(int));
memcpy(temp, pbySalt, 16); // 16바이트를 salt로 채운다.
memcpy(temp + 16, byBlockKey, 4); // 블록키를 iv뒤에 4바이트 이어붙임
// 64바이트 iv hash생성
BYTE ivhash[64] = { 0, };
SHA512_CTX ctx;
SHA512_Init(&ctx);
SHA512_Update(&ctx, temp, 20);
SHA512_Final(ivhash, &ctx);
BYTE iv[32] = { 0, }; // iv는 32바이트이다.
AES_KEY akey;
memcpy(iv, ivhash, 32); // IV 해시에서 32바이트만큼 복사한다.
memset(&akey, 0, sizeof(AES_KEY));
// iv와 대칭키를 설정한다.
if (AES_set_decrypt_key(pbyKey, 256, &akey) < 0)
{
printf("error\n");
return;
}
AES_cbc_encrypt(pbyEncryptedContent + (i*dwBlockSize), (unsigned char*)pbyDecryptedContent+dwRead, dwLen, &akey, iv, AES_DECRYPT);
dwRead += dwLen;
}
}
void Decrypt(
IN PBYTE pbySalt, // salt
IN PBYTE pbyKey, // 복호화용 대칭키
IN PBYTE pbyEncryptedContent, // 암호화 된 컨텐츠
OUT PBYTE pbyDecryptedContent, // 복호화 된 컨텐츠
IN int cbDecryptLength // 복호화 된 검증값 길이
)
{
BYTE iv[32] = { 0, }; // iv는 32바이트이다.
AES_KEY akey;
memcpy(iv, pbySalt, 16); // 16바이트를 salt로 채운다.
memset(&iv[16], 0, 16); // 16바이트 이후를 0으로 채운다.
memset(&akey, 0, sizeof(AES_KEY));
// iv와 대칭키를 설정한다.
if (AES_set_decrypt_key(pbyKey, 256, &akey) < 0)
{
printf("error\n");
return;
}
AES_cbc_encrypt(pbyEncryptedContent, (unsigned char*)pbyDecryptedContent, cbDecryptLength, &akey, iv, AES_DECRYPT);
}
void GenPasswordHash(
OUT PBYTE pbyHash,
IN PWCHAR pszPassword,
IN PBYTE pbySalt)
{
// spin count를 입력해야하기 때문에 UINT형을 사용한다.
UINT32 uiBuffer[(SHA512_DIGEST_LENGTH / sizeof(UINT32)) + 1] = { 0, };
int len = wcslen(pszPassword) * 2; // 패스워드는 유니코드로 처리한다.
SHA512_CTX ctx;
SHA512_Init(&ctx);
SHA512_Update(&ctx, pbySalt, 16);
SHA512_Update(&ctx, pszPassword, len);
SHA512_Final((BYTE*)&uiBuffer[1], &ctx);
// spin_count = 100000
for (int i = 0; i < SPIN_COUNT; i++)
{
*uiBuffer = i; // spin count
SHA512_Init(&ctx);
SHA512_Update(&ctx, &uiBuffer, SHA512_DIGEST_LENGTH + 4 /*spin count buffer length */);
SHA512_Final((BYTE*)&uiBuffer[1], &ctx);
}
memcpy(pbyHash, (BYTE*)&uiBuffer[1], SHA512_DIGEST_LENGTH);
}
void GenAgileEncryptionKey(
IN PBYTE pbyHash, // password 해시
IN PBYTE pbyBlockKey, // 블록키
OUT PBYTE pbyEncKey // 생성된 대칭키
)
{
BYTE byHashWithKey[SHA512_DIGEST_LENGTH + 8] = { 0, };
// password 해시 복사
memcpy(byHashWithKey, pbyHash, SHA512_DIGEST_LENGTH);
// 해시 뒤에 블록키 붙임
memcpy(&byHashWithKey[SHA512_DIGEST_LENGTH], pbyBlockKey, 8);
SHA512_CTX ctx;
SHA512_Init(&ctx);
SHA512_Update(&ctx, byHashWithKey, SHA512_DIGEST_LENGTH + 8);
SHA512_Final(pbyEncKey, &ctx);
}
int _tmain(int argc, _TCHAR* argv[])
{
/*----------------------------
패스워드 검증
-----------------------------*/
// 블록 키 (고정값)
BYTE byHashInputBlockKey[] = { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 };
BYTE byHashValueBlockKey[] = { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e };
// decode64 된 데이터
BYTE bySalt[16] = { 0, };
BYTE byEncryptedVerifierHashInput[16] = { 0, };
BYTE byEncryptedVerifierHashValue[64] = { 0, };
base64_decode(Salt, bySalt, 16);
base64_decode(EncryptedVerifierHashInput, byEncryptedVerifierHashInput, 16);
base64_decode(EncryptedVerifierHashValue, byEncryptedVerifierHashValue, 64);
// 암호화 데이터를 복호화하는 대칭키
BYTE byHashInputKey[SHA512_DIGEST_LENGTH] = { 0, };
BYTE byHashValueKey[SHA512_DIGEST_LENGTH] = { 0, };
// 패스워드 해시를 구함
BYTE byPwHash[64] = { 0, };
GenPasswordHash(byPwHash, Password, bySalt);
// 대칭키 생성 (with password hash)
GenAgileEncryptionKey(byPwHash, byHashInputBlockKey, byHashInputKey);
GenAgileEncryptionKey(byPwHash, byHashValueBlockKey, byHashValueKey);
// 복호화
BYTE byDecryptedVerifierHashInput[16] = { 0, };
BYTE byDecryptedVerifierHashValue[64] = { 0, };
Decrypt(bySalt, byHashInputKey, byEncryptedVerifierHashInput, byDecryptedVerifierHashInput, 16);
Decrypt(bySalt, byHashValueKey, byEncryptedVerifierHashValue, byDecryptedVerifierHashValue, 64);
// 복호화 된 검증용 해시 입력을 해시한다.
BYTE byFinalHash[SHA512_DIGEST_LENGTH] = { 0, };
SHA512_CTX ctx;
SHA512_Init(&ctx);
SHA512_Update(&ctx, byDecryptedVerifierHashInput, 16);
SHA512_Final(byFinalHash, &ctx);
// 해시 비교한다.
int nCmp = memcmp(byFinalHash, byDecryptedVerifierHashValue, SHA512_DIGEST_LENGTH);
if (0 == nCmp) printf("correct password!\n");
else printf("incorrect password!\n");
/*----------------------------
문서 복호화
-----------------------------*/
// 블록 키 (고정값)
BYTE byKeyBlockKey[] = { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 };
BYTE* byEncryptedFileBuffer = NULL;
BYTE* byDecryptedFileBuffer = NULL;
byEncryptedFileBuffer = (BYTE*)malloc(MAX_FILE_SIZE);
byDecryptedFileBuffer = (BYTE*)malloc(MAX_FILE_SIZE);
UINT64* pullDecryptLength = (UINT64*)malloc(sizeof(UINT64));
// EncryptPackage 파일 오픈
HANDLE hFile = CreateFile(L"D:\\EncryptedPackage2", GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
DWORD dwRead = 0;
ReadFile(hFile, byEncryptedFileBuffer, MAX_FILE_SIZE, &dwRead, NULL);
// 앞 8바이트에서 복호화 길이를 구한다.
memcpy(pullDecryptLength, byEncryptedFileBuffer, 8);
BYTE bySaltValue[16] = { 0, };
BYTE byEncryptedKeyValue[32] = { 0, };
base64_decode(SaltValue, bySaltValue, 16);
base64_decode(EncryptedKeyValue, byEncryptedKeyValue, 32);
// 대칭키 생성 (with password hash)
BYTE byKeyValueKey[SHA512_DIGEST_LENGTH] = { 0, };
GenAgileEncryptionKey(byPwHash, byKeyBlockKey, byKeyValueKey);
// 대칭키를 복호화한다.
BYTE byDecryptedKeyValue[32] = { 0, };
Decrypt(bySalt, byKeyValueKey, byEncryptedKeyValue, byDecryptedKeyValue, 32);
HANDLE hSaveFile = CreateFile(L"D:\\Decrypt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
// 문서를 복호화한다.
Decrypt2(bySaltValue, byDecryptedKeyValue, &byEncryptedFileBuffer[8], byDecryptedFileBuffer, *pullDecryptLength);
DWORD dwWritten = 0;
WriteFile(hSaveFile, byDecryptedFileBuffer, *pullDecryptLength, &dwWritten, NULL);
free(pullDecryptLength);
getchar();
return 0;
}
'etc' 카테고리의 다른 글
컴파일러별 CPU 전처리기 모음 (0) | 2017.10.19 |
---|---|
패킷으로 보는 초간략 HTTP 통신 과정 (TCP 계층) (3) | 2016.01.21 |
ubuntu 14.04.3 & windows 10 uefi dual-boot (0) | 2016.01.04 |
MSI GE72-6QF 리눅스 설치기 (0) | 2015.12.25 |