[개발] 암복호화 aes-128-ecb / java, nodejs

개발하다보면 이게 개발 언어가 다른 경우가 있음.

특히 암복호화 같은게 이기종에서 자주 씀. 그래서 기록용으로 남겨 봄.
( nodejs 의 결과와 java 의 결과가 동일 해야 됨 )

누군가에게 도움이 되길 ...

NODEJS

////////////////////////////////
//
//  info
//
//  빠른 테스트를 위한 nodejs 용 api 호출 및 암복호화 처리 샘플
//  

////////////////////////////////
//
//  require
//

require('dotenv').config;                       // npm i dotenv - optional / .env 설정 정보 로딩용 

const crypto = require('crypto');               // default - 암복호화 처리

const dateformat = require('dateformat');       // npm i dateformat - date 유틸 

////////////////////////////////
//
//  const
//

const API_SECRET_KEY = process.env.API_SECRET_KEY || '012345678901234567890123456789ab';

const CRYPTO_CONFIG = {
    ALGORITHM : 'aes-128-ecb',
    IV : '',
    HEX : 'hex',
    UTF_8 : 'utf-8',
};

////////////////////////////////
//
//  private function 
//

/**
 * 로그를 기록한다
 * @param  {...any} args 파라미터 
 * @since 2021.02.24
 */
const _log = (...args) => {
    let df = dateformat(new Date(), `yy-mm-dd HH:MM:ss`);
    console.log(`[${df}] `, ...args);
}

////////////////////////////////
//
//  public function 
//

/**
 * 암호화를 수행한다 
 * @param {String} plainText 평문 
 * @param {String} key 암복호화 키
 * @since 2021.02.24
 */
function aes_encrypt(plainText, key = API_SECRET_KEY){
    let hexKey = Buffer.from(key, CRYPTO_CONFIG.HEX);
    let cipher = crypto.createCipheriv(CRYPTO_CONFIG.ALGORITHM, hexKey, CRYPTO_CONFIG.IV);
    let encrypted = cipher.update(plainText,CRYPTO_CONFIG.UTF_8,CRYPTO_CONFIG.HEX);
    encrypted += cipher.final(CRYPTO_CONFIG.HEX);
    return encrypted;
}

/**
 * 복호화를 수행한다 
 * @param {String} encText 암호화 된 HEX 문자열 
 * @param {String} key 암복호화 키 
 * @since 2021.02.24
 */
function aes_decrypt(encText, key = API_SECRET_KEY){
    let hexKey = Buffer.from(key, CRYPTO_CONFIG.HEX);
    let cipher = crypto.createDecipheriv(CRYPTO_CONFIG.ALGORITHM, hexKey, CRYPTO_CONFIG.IV);
    let decrypted = cipher.update(encText,CRYPTO_CONFIG.HEX, CRYPTO_CONFIG.UTF_8);
    decrypted += cipher.final(CRYPTO_CONFIG.UTF_8);
    return decrypted;
}

////////////////////////////////
//
//  entry point 
//

/**
 * 진입점
 * @since 2021.02.24
 */
async function init(){

    // ----- 1. 암복호화 테스트 ------
    _log('1. 암복호화 테스트');

    let plain_text = "가나다";
    _log('plain_text', plain_text);

    let encrypt_text = aes_encrypt(plain_text);
    _log('encrypt_text', encrypt_text);

    let decrypt_text = aes_decrypt('c898d3081b18a58c0f6bbbab927591275c30a9532ea96b925c85133aceedec4c');
    _log('decrypt_text', decrypt_text);
}
init();

JAVA

package sample;

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

/**
 * AES 암복호화 유틸리티
 * @author wonsama
 * @see https://boneman.tistory.com/entry/ByteUtiljava
 */
public class AESCrypto {
    
    private String cipherKey;
    
    
    /**
     * 생성자
     * @param cipherKey 암/복호화 키
     * @since 2021.02.24
     */
    public AESCrypto(String cipherKey){
        this.cipherKey = cipherKey;
    }
    
    /**
     * 모드에 따른 암복호화 처리기
     * @param mode Cipher.ENCRYPT_MODE / Cipher.DECRYPT_MODE
     * @return Cipher
     * @throws Exception - NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException
     * @since 2021.02.24
     */
    private Cipher getCipher(int mode) throws Exception{
        Key key = new SecretKeySpec(toBytes(cipherKey, 16), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(mode, key);
        
        return cipher;
    }

    /**
     * AES(aes-128-ecb)암호화
     * @param src 평문
     * @return 암호화 된 HEX문자열
     * @throws Exception
     * @since 2021.02.24
     */
    public String encrypt(String src) throws Exception {
        Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
        byte[] plain = src.getBytes();
        byte[] encrypt = cipher.doFinal(plain);
        return toHexString(encrypt);
    }

    /**
     * AES(aes-128-ecb)복호화 
     * @param hex 암호화 된 HEX문자열 
     * @return 복호화 된 평문
     * @throws Exception
     * @since 2021.02.24
     */
    public String decrypt(String hex) throws Exception {
        Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
        byte[] encrypt = toBytesFromHexString(hex);
        byte[] decrypt = cipher.doFinal(encrypt);
        return new String(decrypt);
    }

    /**
     * 8, 10, 16진수 문자열을 바이트 배열로 변환한다. 
     * 8, 10진수인 경우는 문자열의 3자리가, 16진수인 경우는 2자리가, 하나의 byte로 바뀐다.
     * @param digits 문자열 
     * @param radix 진수 (8,10,16만 가능)
     * @return byte[]
     * @throws Exception - NumberFormatException, IllegalArgumentException
     */
    private byte[] toBytes(String digits, int radix) throws Exception {
        if (digits == null) {
            return null;
        }
        if (radix != 16 && radix != 10 && radix != 8) {
            throw new IllegalArgumentException("For input radix: \"" + radix+ "\"");
        }
        int divLen = (radix == 16) ? 2 : 3;
        int length = digits.length();
        if (length % divLen == 1) {
            throw new IllegalArgumentException("For input string: \"" + digits+ "\"");
        }
        length = length / divLen;
        byte[] bytes = new byte[length];
        for (int i = 0; i < length; i++) {
            int index = i * divLen;
            bytes[i] = (byte) (Short.parseShort(digits.substring(index, index + divLen), radix));
        }
        return bytes;
    }

    /**
     * 입력받은 HEX 문자열을 byte[] 로 변환
     * @param digits 입력 문자열 
     * @return byte[]
     * @throws Exception - IllegalArgumentException, NumberFormatException
     */
    private byte[] toBytesFromHexString(String digits) throws Exception {
        if (digits == null) {
            return null;
        }
        int length = digits.length();
        if (length % 2 == 1) {
            throw new IllegalArgumentException("For input string: \"" + digits + "\"");
        }
        length = length / 2;
        byte[] bytes = new byte[length];
        for (int i = 0; i < length; i++) {
            int index = i * 2;
            bytes[i] = (byte) (Short.parseShort(digits.substring(index, index + 2), 16));
        }
        return bytes;
    }

    /**
     * unsigned byte(바이트) 배열을 16진수 문자열로 바꾼다.
     * @param bytes 배열 
     * @return 16진수 문자열
     */
    private String toHexString(byte[] bytes) {
        if (bytes == null) {
            return null;
        }

        StringBuffer result = new StringBuffer();
        for (byte b : bytes) {
            result.append(Integer.toString((b & 0xF0) >> 4, 16));
            result.append(Integer.toString(b & 0x0F, 16));
        }
        return result.toString();
    }   
}

Java for test

package sample;

public class Main {
    
    public static void main(String[] args) {
        try {
            new Main();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    Main() throws Exception{
        AESCrypto aes = new AESCrypto("012345678901234567890123456789ab");
        
        String plainText = "가나다";
        String encText = "e49bd4ca6f6717a22edcbb353c9a57da";
        String decText = "";
        
        // 평문
        System.out.println(String.format("plain : %s", plainText));
        
        // 암호화 
        encText = aes.encrypt(plainText);
        System.out.println(String.format("encrypt : %s", encText));
        
        // 복호화
        decText = aes.decrypt(encText);
        System.out.println(String.format("descypt : %s", decText));
    }
}

참조링크

Sort:  

@wonsama transfered 4 KRWP to @krwp.burn. voting percent : 9.56%, voting power : 89.47%, steem power : 1715320.55, STU KRW : 1200.
@wonsama staking status : 822.929 KRWP
@wonsama limit for KRWP voting service : 2.468 KRWP (rate : 0.003)
What you sent : 4 KRWP
Refund balance : 1.532 KRWP [51484800 - bb94aa8347f599c771138a8382bbbdbbc3b2ae8b]

Coin Marketplace

STEEM 0.19
TRX 0.15
JST 0.029
BTC 63248.94
ETH 2576.33
USDT 1.00
SBD 2.85