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);
}
*/