SecureCRT는 비밀번호를 암호화 하여 저장한다. 비밀번호 암호화에는 총 3가지 버전이 쓰인다고 알려져 있다. 그중, v2가 현 시점에서 가장 많이 쓰이는 것 같다.
이들을 암호화·복호화 하는 방법은 인터넷에 공개되어 있다.
아쉽게도 폐쇄망에서는 이런 코드를 바로 옮겨쓰기가 힘들다. dependency의 문제가 크다. python 코드의 경우, pycryptodome
을 설치해라는데, 폐쇄망에서는 사실상 불가하다.
그렇기 때문에, standalone한 프로그램을 만드려면 node를 반입해야 한다. node에는 crypto
라는 모듈이 같이 제공된다.
v1
const crypto = require('crypto');
function decrypt(password) {
const key1 = Buffer.from('5F B0 45 A2 94 17 D9 16 C6 C6 A2 FF 06 41 82 B7'.replace(/ /g, ''), 'hex');
const key2 = Buffer.from('24 A6 3D DE 5B D3 B3 82 9C 7E 06 F4 08 16 AA 07'.replace(/ /g, ''), 'hex');
const iv = Buffer.alloc(8, 0x00); // 8 zero bytes for the IV
// Decode the password from hex
const passwordBytes = Buffer.from(password, 'hex');
// First decryption with key2
const decipher2 = crypto.createDecipheriv('bf-cbc', key2, iv);
decipher2.setAutoPadding(false); // Disable automatic padding
let intermediate = decipher2.update(passwordBytes);
intermediate = Buffer.concat([intermediate, decipher2.final()]);
// Remove the first and last 4 bytes
intermediate = intermediate.slice(4, -4);
// Second decryption with key1
const decipher1 = crypto.createDecipheriv('bf-cbc', key1, iv);
decipher1.setAutoPadding(false); // Disable automatic padding
let padded = decipher1.update(intermediate);
padded = Buffer.concat([padded, decipher1.final()]);
// Extract the password until two consecutive zero bytes are found
let p = Buffer.alloc(0);
let i = 0;
while (i + 1 < padded.length) {
if (padded[i] === 0x00 && padded[i + 1] === 0x00) {
break;
}
p = Buffer.concat([p, padded.slice(i, i + 2)]);
i += 2;
}
// Decode the password from UTF-16LE encoding
const result = p.toString('utf16le');
return result;
}
// Example usage:
// Replace 'your_encrypted_password_hex' with the actual encrypted password in hex format
// const decryptedPassword = decrypt('your_encrypted_password_hex');
// console.log('Decrypted Password:', decryptedPassword);
v2
const crypto = require('crypto');
function decryptV2(ciphertextHex, configPassphrase = '', prefix = '02') {
const ciphertextBytes = Buffer.from(ciphertextHex, 'hex');
// Key is SHA256 hash of config passphrase (기본적으로는 공백임)
const key = crypto.createHash('sha256').update(configPassphrase, 'utf8').digest();
const iv = Buffer.alloc(16, 0x00); // 16 zero bytes
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
decipher.setAutoPadding(false);
let decrypted = Buffer.concat([decipher.update(ciphertextBytes), decipher.final()]);
// Process decrypted data (same as previous code)
const plaintextLength = decrypted.readUInt32LE(0);
const expectedLength = 4 + plaintextLength + 32; // 32 bytes for SHA256 digest
if (decrypted.length < expectedLength) {
throw new Error('Invalid ciphertext: incorrect plaintext length.');
}
const plaintextBytes = decrypted.slice(4, 4 + plaintextLength);
const checksumBytes = decrypted.slice(4 + plaintextLength, 4 + plaintextLength + 32);
const computedDigest = crypto.createHash('sha256').update(plaintextBytes).digest();
if (!computedDigest.equals(checksumBytes)) {
throw new Error('Invalid ciphertext: checksum does not match.');
}
const password = plaintextBytes.toString('utf8');
return password;
}
// Example usage:
/*
try {
const decryptedPassword = decryptV2('your_encrypted_password_hex', 'your_config_passphrase', '02');
console.log('Decrypted Password:', decryptedPassword);
} catch (error) {
console.error('Decryption failed:', error.message);
}
*/
v2 암호화는 다음과 같이 가능하다
const crypto = require('crypto');
function encryptV2(plaintext, configPassphrase = '') {
const plaintextBytes = Buffer.from(plaintext, 'utf8');
if (plaintextBytes.length > 0xffffffff) {
throw new Error('Bad plaintext: too long!');
}
// Key is SHA256 hash of the config passphrase
const key = crypto.createHash('sha256').update(configPassphrase, 'utf8').digest();
const iv = Buffer.alloc(16, 0x00); // 16 zero bytes
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
// Create the LVC (length, value, checksum)
const lvcBytes = Buffer.concat([
Buffer.alloc(4), // 4-byte little-endian length
plaintextBytes, // plaintext bytes
crypto.createHash('sha256').update(plaintextBytes).digest() // SHA256 digest of plaintext
]);
lvcBytes.writeUInt32LE(plaintextBytes.length, 0); // Write the length at the start
// Calculate padding length and add random padding
let paddingLength = 16 - (lvcBytes.length % 16);
if (paddingLength < 16 / 2) {
paddingLength += 16;
}
const paddingBytes = crypto.randomBytes(paddingLength);
// Concatenate LVC bytes and padding bytes
const paddedLVC = Buffer.concat([lvcBytes, paddingBytes]);
// Encrypt the padded LVC
const ciphertextBytes = Buffer.concat([cipher.update(paddedLVC), cipher.final()]);
// Return the hex string of the ciphertext (no salt for prefix '02')
return ciphertextBytes.toString('hex');
}
// Example usage:
/*
const plaintext = 'your_plaintext';
const configPassphrase = 'your_config_passphrase';
try {
const ciphertextHex = encryptV2(plaintext, configPassphrase, '02');
console.log('Encrypted ciphertext:', ciphertextHex);
} catch (error) {
console.error('Encryption failed:', error.message);
}
*/