AES 암복호화

대상 언어 플랫폼

  • Net, Java, Node, C++

암복호화 기준

AES

  • BlockSize: 128 bits
  • KeySize: 256 bits
  • Key: PBKDF2 로 생성된 Key
  • 암호화 시 IV: 랜덤 16바이트
  • 복호화 시 IV: 암호화 된 문자열을 Base64로 인코딩한 바이트 시퀀스의 앞 16자리
  • Padding: PKCS7
  • 작업모드: CBC
  • 암호화 결과: IV(16바이트)에 암호화된 바이트 시퀀스가 합쳐진 값이 base-64로 디코딩 된 문자열

key 생성 알고리즘

  • PBKDF2 사용
  • Password : 지정된 암호
  • Salt : 암호를 UTF8로 인코딩한 바이트 시퀀스를 SHA256으로 해시한 바이트 시퀀스
  • Iteration Count : 1000
  • 해시 알고리즘 : SHA1
    • SHA1은 보안상 위험하나 .Net std 2.0에서는 SHA1만 지원하기 때문에 SHA1을 사용
    • MS에서는 보안문제로 SHA1보다는 SHA256이상을 권고
    • .Net std 2.0 상위 버전에서는 SHA256 사용 가능

Node

- 암호화 (ENCRYPT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const crypto = require('crypto');
const Rijndael = require('rijndael-js');
const padder = require('pkcs7-padding');

const plainText = '평문 문자열';
const password = '지정된 암호';

const salt = crypto.createHash('sha256').update(Buffer.from(password)).digest();
const key = await new Promise((resolve) => {
crypto.pbkdf2(password, salt, 1000, 32, 'sha1', (err, derivedKey) => {
if (err) { throw err; }

resolve(derivedKey);
});
});

const shuffle = (arr) => {
let x, t, r = new Uint32Array(1);

for (let i = 0, c = arr.length - 1, m = arr.length; i < c; i++, m--) {
crypto.webcrypto.getRandomValues(r);

x = Math.floor((r / 65536 / 65536) * m) + i;
(t = arr[i]), (arr[i] = arr[x]), (arr[x] = t);
}

return arr;
};

const iv = Buffer.from(shuffle([...plainText]).join('')).slice(0, 16);

const rijndael = new Rijndael(key, 'cbc');
const padded = padder.pad(plainText, 32);
const encrypted = rijndael.encrypt(padded, '128', iv);
const ciphertext = Buffer.concat([iv, Buffer.from(encrypted)]).toString('base64');

console.log(ciphertext);

- 복호화 (DECRYPT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const crypto = require('crypto');
const Rijndael = require('rijndael-js');
const padder = require('pkcs7-padding');

const cipherText = '암호화 된 문자열';
const password = '지정된 암호';

const salt = crypto.createHash('sha256').update(Buffer.from(password)).digest();
const key = await new Promise((resolve) => {
crypto.pbkdf2(password, salt, 1000, 32, 'sha1', (err, derivedKey) => {
if (err) { throw err; }

resolve(derivedKey);
});
});

const ivAndEncrypted = Buffer.from(cipherText, 'base64');

const iv = ivAndEncrypted.slice(0, 16);
const encrypted = ivAndEncrypted.slice(16, ivAndEncrypted.byteLength);

const rijndael = new Rijndael(key, 'cbc');
const decrypted = rijndael.decrypt(encrypted, '128', iv);
const decryptedPadded = padder.unpad(decrypted, 32);
const plaintext = Buffer.from(decryptedPadded.filter((buf) => buf > 11)).toString('utf8');

console.log(plaintext);

아래 제공된 비밀번호와 원본 문자열로 Node로 암호화 된 문자열을 다른 언어의 AES로 복호화가 되는지 검증 할 수 있다.

  • 원본 문자열: 123Apple.456TENET@Node
  • 암호: P@ssw0rd.Node
  • 암호화 된 문자열: NVRONGwzRUUuQVQyZG9AZVawSBA0/3VPmOnljnL+5HhOariKM5KGZwJbe3Zi48ca
공유하기