Microsoft 오피스 패스워드 검증과 문서 암호 해제
 
오피스 문서 포맷에 걸린 패스워드 검증과 문서 암호 해제에 대하여 알아봅니다.
 
테스트 환경 : Windows 7 & Office 2013 (엑셀 및 워드)
 
# 패스워드 검증 
 
먼저, 패스워드를 걸은 문서를 준비합니다.
오피스에서 패스워드를 설정할때 읽기 패스워드와 쓰기 패스워드를 설정해야 하는데 본 포스팅에서는 읽기 패스워드 검증만 다룹니다.
 
SSView.zip
다운로드

오피스 문서의 구조를 볼 수 있는 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>
 
아래 링크에 각 항목에 대한 설명이 있습니다.
 
읽기 패스워드를 검증하기 위해선 아래의 keyEncryptor 구조만 보면 됩니다. 
대략 살펴보면 반복 해시 10만번, salt 16바이트, AES256-CBC, SHA512 정도가 보입니다.
해당 정보들은 오피스 버전마다 다릅니다. 버전이 올라갈수록 암호화 강도도 세집니다.
(오피스 2007의 경우 반복 해시 0번에 SHA을 사용합니다.)
 
패스워드를 검증하는데 필요한 항목은 saltValue, encryptedVerifierHashInput, encryptedVerifierHashValue 이 세가지 입니다. 디코드하면 각각 16바이트, 16바이트, 64바이트입니다. 
 
salt는 사용자 입력 패스워드에 덧붙이는 랜덤 난수 생성 값으로 복호화 시 IV에 사용됩니다.
encryptedVerifierHashInput와 encryptedVerifierHashValue는 암호화 된 검증용 값들로 패스워드 해시로 유도한 대칭키로 복호화 한 후 패스워드가 올바른지 검증하는데 사용합니다.
 
패스워드를 검증하는 일련의 과정을 간략하게 도식화하면 아래와 같습니다.

 

[키 생성]

 

[키 검증]

 

첫번째 그림은 키 생성부입니다. 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) 일치하면 올바른 패스워드입니다. 

 
# 문서 암호 해제 
 
복호화를 하기 전에 원본 문서 길이(복호화 길이)와 암호화된 문서 데이터를 알아야 합니다.
 
 
문서의 구조를 보면 EncryptedPackage 라는 항목이 있는데 원본 문서를 암호화 한 데이터가 들어가 있는 항목입니다.
첫번째 8바이트는 복호화 길이로 0x2071 = 8305바이트를 포함한다고 명시되어 있습니다.
암호화 된 데이터의 위치는 오프셋 0x8부터 끝까지입니다.
 
다시 xml구조를 봅니다. 문서를 복호화하는데 필요한 항목은 패스워드 검증할 때와 조금 다릅니다. 
keyEncryptor 구조의 encryptedKeyValue와 keyData 구조의 saltValue만 있으면 됩니다.
 
encryptedKeyValue는 암호화 된 대칭키입니다. 이 키로 문서를 복호화합니다. saltValue는 IV를 생성할 때 사용하는 salt입니다. 디코드를 하면 각각 32바이트, 16바이트입니다.
 
복호화 시 중요한 점은 문서 전체를 한번에 복호화 하는 것이 아닌 블록 단위 (4096바이트)로 합니다. 이 과정에서 IV는 매번 다른 값을 사용합니다.
 
1) key 블록키를 패스워드 해시 뒤에 붙이고 해시합니다. 해당 해시는 encryptedKeyValue의 대칭키가 됩니다. (블록키는 소스를 참조)
 
2) 1)에서 얻어낸 키로 복호화를 하여 32바이트 대칭키를 얻습니다.
 

 

3) IV연산은 16바이트 salt 에 4바이트 블록키를 더해서 생성합니다. 디폴트 블록키는 0이며 한 사이클당 1씩 증가합니다.

4) salt+블록키 = 20바이트를 해시합니다.

5) 해시된 데이터를 32바이트 컷합니다. (해시에 1 2 3  ... 64가 담겼다고 가정할 경우 32에서 컷) 해당 데이터를 IV로 사용합니다.
 
6) 대칭키와 IV, 그리고 암호화된 문서 블록(4096바이트)를 가지고 복호화합니다.
 
7) 3)부터 N번만큼 복호화를 반복합니다. 마지막 복호화의 길이는 원본문서길이 % 블록 사이즈입니다. 원본 길이가 8305이면 N은 3, 마지막 암/복호화 길이는 113입니다.
 
복호화된 데이터는 오피스에서 바로 오픈할 수 있습니다.
 
#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;
}

 

클라이언트 : 192.168.123.129 (Windows 7 + chrome)

서버 : 192.168.123.127 (Ubuntu + apache 기본 설정)

클라이언트의 웹 브라우저에서 http://192.168.123.127을 입력했을 시 TCP계층에서 어떤 데이터들이 오고가는지 와이어샤크를 통해서 간략하게 살펴봅니다. 

 와이어샤크 패킷 다운로드 

wireshark.pcapng


[클릭클릭 확대해서 봅니다]


[간략 표]



#1. 3-way 핸드셰이크

SYN : 통신 개시를 요청

ACK : 통신 확인에 대해 알겠다고 확인 응답

[No. 1] 클라이언트는 서버에게 통신 시작을 요청합니다. (SYN)

[No. 2] 서버는 알겠다고 응답함과 동시에 클라이언트에게 통신 시작을 요청합니다. (SYN & ACK)

[No. 3] 클라이언트는 서버에게 알겠다고 응답합니다. (ACK)


이 일련의 과정을 3-way 핸드셰이크라 합니다.


#2. web page 요청 및 다운로드

[No. 4] 악수(?)가 끝나고 난 후 클라이언트는 웹 페이지를 요청합니다.

[No. 5] 서버는 알겠다고 응답합니다.

[No. 6~14] 서버가 index.html을 클라이언트에 전송합니다. 총 7517바이트를 전송하는데 한번에 보내지 않고 잘라서 전송을 합니다. 여기서는 6개로 자르는데 각각의 덩어리를 세그먼트라 칭합니다. 여기서는 각 세그먼트당 1460바이트입니다. (1460바이트 * 5개 + 마지막 세그먼트 217바이트) 덩어리로 자른 후에 번호를 붙이게 되는데 0,1,2,3,4번 이렇게 순차적인 번호를 붙이는게 아니라 바이트로 번호를 매깁니다. 1, 1461, 2921, 4381, 5841, 7301번으로 나뉘어 있습니다. 이를 Sequence 번호라 합니다. 좀 더 부연설명을 하자면 xxx번 포트에서 연결 유지 중인 전체 데이터 중 몇 번째 바이트에 해당하는지 인덱싱하는 것입니다. 여기서는 이미 핸드셰이크에서 1바이트(No.2 패킷)가 추가되었으므로 0번 세그먼트가 아닌 1번 세그먼트(1바이트)부터 시작하는 것 입니다. 서버와 클라이언트의 Sequence 번호는 각각 인덱싱됩니다.


여기서는 서버가 2개의 덩어리를 보낸 후 제대로 데이터가 전달됐는지 확인응답을 받는 구조입니다. [No.8] 응답 패킷을 보면 Acknowledgment 번호(다음에 상대방으로부터 받아야 할 Sequence 번호)가 2921번이라고 나오는데 이는 서버로부터 2921바이트(1460바이트*2 + 1바이트(No.2 패킷의 SYN비트))까지 정상적으로 다운로드 하였다는 것을 의미합니다. 0번째바이트부터 계산되므로 2920번째 바이트까지라고 보면됩니다. (서버야 난 2920번째 바이트까지 무사히 받았고 그 다음엔 2921번째가 필요하니 패킷 내놔라)

그런데 잠깐.. 와이어샤크 덤프에서는 세그먼트는 5개만 보이는데 마지막 7301번은 어디에 있을까요? 그것은 200 응답의 [No. 13]의 패킷에 포함되어 있습니다.

No.13 패킷에는 분리된 세그먼트에 대한 정보도 포함되어 있습니다.

이 정보를 바탕으로 클라이언트에서 세그먼트들을 다시 합칩니다. 합친 내용을 아래 그림과 같이 브라우저가 출력합니다. 

[No. 13 패킷의 일부분]

추가적으로 [No. 4] & [No. 14]에 PSH (PUSH 비트)가 붙었는데 이는 애플리케이션 계층으로 토스하라는 의미입니다. (즉, Apache 혹은 크롬으로 토스)


#3. 통신 종료


FIN : 통신 종료를 요청

RST : 통신을 강제로 해제

[No. 15] 서버는 클라이언트에게 통신을 끝내자고 요청합니다. (FIN & ACK)

[No. 16] 클라이언트는 알겠다고 응답합니다. (ACK)

<< 16과 17사이에 수초 간의 텀이 있습니다. >> 

[No. 17] 클라이언트는 서버에게 통신을 끝내자고 요청합니다. (FIN & ACK)

[No. 18] 클라이언트는 통신을 강제로 해제합니다. (RST & ACK) (의도되지 않은 게 올라왔네요. 무시합니다.)

[No. 19] 서버는 클라이언트에게 알겠다고 응답합니다. No.17에 대한 응답입니다. (ACK)



개인키 생성

openssl genrsa -out privatekey.pem 1024

개인키 정보 출력

openssl rsa -in privatekey.pem -text -out private.txt

modulus : 00을 제외하고 128개, 128*8 = 1024bit

prime1, prime2 : 00을 제외하고 64개, 64*8 = 512bit

modulus(N) = prime1(P) * prime2(Q)

공개키 : publicExponent(65537), N

개인키 : privateExponent(D), N

exponent1 : D mod (P-1)

exponent2 : D mod (Q-1)

coefficient : (inverse of Q) mod P

공개키 생성

openssl pkey -in privatekey.pem -pubout -out publickey.pem

공개키 정보 출력

openssl pkey -in privatekey.pem -text_pub


64비트 키 검증

http://www.mathpapa.com/algebra-calculator.html 로 계산 가능

Private-Key: (64 bit)

modulus: 00:c6:cf:f1:d2:1c:ba:3d:bb

publicExponent: 65537 (0x10001)

privateExponent: 0f:04:29:c9:88:d9:02:61

prime1: 3825875939 (0xe40a3be3)

prime2: 3744484937 (0xdf304e49)

exponent1: 553066949 (0x20f721c5)

exponent2: 2212512545 (0x83e04321)

coefficient: 2971090683 (0xb1173efb) 


p * q = modulus = 14325934824416231424 (C6CFF1D21CBA3DBB)



+ Recent posts