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