스프링 부트

[SpringBoot] Encrypt - AES128 CBC(with iv) example code

h__hj 2022. 10. 3. 18:03

# Encrypt - AES128 CBC(with iv) example code

화면 요청 에서 부터 시작한 암호화 복호화!!

대략적인 test code를 확인하려면 아래 링크에서 확인 가능 합니다.

# 환경

# tool: STS 4.13.0
# version: 2.7.3-SNAPSHOT
# java: 11
# type: MAVEN
# view: THYMELEAF
# jQuery: 3.6.0

# 페이지

# PROPERTIES FILE

## CRYPTO
crypto.aes.algorithm  = AES/CBC/PKCS5Padding
crypto.aes.iv         = ENC(a4FahIzQQNWuhPMun12NSicmzNULaZ4RO1IhtzQixAo=)
crypto.aes128.key     = ENC(ASyujlUDkqjrnCQBRK+vpdVYUZOr0dTqG6mFGmlthpE=)
crypto.aes256.key     = ENC(zDZ8Qg97908xPP0K7NQ32Ku9JYTb+ur+6Oc+R6gLgKpt0EsOJb5huipD7hjuYkaq)

# Configuration

@Configuration
public class CryptoConfig {
	
    public final static String DEFAULT_CHAR_SET = "UTF-8";
	
    @Value("${crypto.aes.algorithm}") 
    private String aesAlgorithm;
    @Value("${crypto.aes.iv}") 
    private String aesIv;
    @Value("${crypto.aes128.key}") 
    private String aes128SecretKey;
	
    @Bean("aes128Encryptor")
    public Cipher aes128Encryptor() {
        try {
            SecretKeySpec secretKey = new SecretKeySpec(aes128SecretKey.getBytes(DEFAULT_CHAR_SET), "AES");
            IvParameterSpec secretIv = new IvParameterSpec(aesIv.getBytes(DEFAULT_CHAR_SET));
			
            Cipher cipher = Cipher.getInstance(aesAlgorithm);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, secretIv);
			
            return cipher;
        } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException| UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
	
    @Bean("aes128Decryptor")
    public Cipher aes128Decryptor() {
        try {
            SecretKeySpec secretKey = new SecretKeySpec(aes128SecretKey.getBytes(DEFAULT_CHAR_SET), "AES");
            IvParameterSpec secretIv = new IvParameterSpec(aesIv.getBytes(DEFAULT_CHAR_SET));
			
            Cipher cipher = Cipher.getInstance(aesAlgorithm);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, secretIv);
			
            return cipher;
        } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException| UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

# Service

@Service
public class EncryptService {
	
    @Autowired
    private Cipher aes128Encryptor;
    @Autowired
    private Cipher aes128Decryptor;
	
    /**
     * AES 128 CBC WITH IV ENCRYPT
     */
    public String encryptAes128(String decryptStr, String charSet) {
        String encryptStr = "";
        try {
            byte[] encryptBytes = aes128Encryptor.doFinal(decryptStr.getBytes(charSet));
			
            encryptStr = Base64.getEncoder().encodeToString(encryptBytes); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptStr;
    }
    
    /**
     * AES 128 CBC WITH IV DECRYPT
     */
    public String decryptAes128(String encryptStr, String charSet) {
        String decryptStr = "";
        try {
            byte[] encryptBytes = Base64.getDecoder().decode(encryptStr);
            byte[] decryptBytes = aes128Decryptor.doFinal(encryptBytes);
            decryptStr = new String(decryptBytes, charSet);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decryptStr;
    }	
}

# Controller

@Slf4j
@Controller
@RequestMapping("/test/encrypt")
public class TestEncryptController {

    @Autowired
    private EncryptService encryptService;
		
    @PostMapping("/aes/{num}/{type}")
    @ResponseBody
    public Map<String, String> aes(@PathVariable String num, @PathVariable String type, @RequestParam String before) {
        log.debug("[PARAMETER] {}", before);
        String after = "";
		
        if(StringUtils.isNotEmpty(before)) {
            if("256".equals(num) && "enc".equals(type)) {
                after = encryptService.encryptAes256(before, CryptoConfig.DEFAULT_CHAR_SET);
				
            } else if("256".equals(num) && "dec".equals(type)) {
                after = encryptService.decryptAes256(before, CryptoConfig.DEFAULT_CHAR_SET);
				
            } else if("128".equals(num) && "enc".equals(type)) {
                after = encryptService.encryptAes128(before, CryptoConfig.DEFAULT_CHAR_SET);
				
            } else if("128".equals(num) && "dec".equals(type)) {
                after = encryptService.decryptAes128(before, CryptoConfig.DEFAULT_CHAR_SET);
            } 
        }
		
        Map<String, String> response = new HashMap<String, String>();
        response.put("before", before);
        response.put("after", after);
        return response;
    }
}

# HTML

<div>
    <h3>AES Encrypt/Decrypt</h3>
    <input type="text" id="encrypt" placeholder="Encrypt Text" style="display: inline;"/>
    <input type="button" onclick="test.crypto('256', 'enc')" value="ENCRYPT256" style="display: inline;"/>
    <input type="button" onclick="test.crypto('128', 'enc')" value="ENCRYPT128" style="display: inline;"/>
    <br>
    <input type="text" id="decrypt" placeholder="Decrypt Text" style="display: inline;"/>
    <input type="button" onclick="test.crypto('256', 'dec')" value="DECRYPT256" style="display: inline;"/>
    <input type="button" onclick="test.crypto('128', 'dec')" value="DECRYPT128" style="display: inline;"/>
    <br>
</div>

# javascript (jQuery)

const test = {
    crypto: function(num, type) {
        const input = {
            before: (type === 'enc') ? $('#encrypt').val() : $('#decrypt').val()
        }
        AjaxUtils.sendPost(`/test/encrypt/aes/${num}/${type}`, input, 
            function(result) {
                console.log("result:", result.before);
                console.log("result:", result.after);
            }
        );
    },
}

const AjaxUtils = {
    sendPost: function(url, input, cbSuccess) {
        $.ajax(url, { 
        // options
            method      : "POST",
            data        : input,	
            dataType    : "json",
        // success
        }).done(function(output, _, _) {// 1:data, 2:textStatus, 3:jqXHR
            cbSuccess(output);
        })    
    }
}

# LOG

// ENCRYPT128 Click!!
// Controller
// request: ["128","enc","TEST CODE"]
[PARAMETER] TEST CODE
// response: {"before":"TEST CODE","after":"KocYFcSNxooeTIpCIEdg8Q=="}

// JAVASCRIPT
result: TEST CODE
result: KocYFcSNxooeTIpCIEdg8Q==

// DECRYPT128 Click!!
// Controller
// request: ["128","dec","KocYFcSNxooeTIpCIEdg8Q=="]
[PARAMETER] TEST CODE
// response: {"before":"KocYFcSNxooeTIpCIEdg8Q==","after":"TEST CODE"}

// JAVASCRIPT
result: KocYFcSNxooeTIpCIEdg8Q==
result: TEST CODE

# 마치며.

  • https://hjho95.tistory.com/25 에 있는 내용을 보게 되면
  • 블로그 본문 테스트 코드 처럼 iv 값을 고정된 값으로 사용하면 문제가 될 수 있다는 내용이 인용되어 있다.
  • 블로그 본문은 aes128 의 cbc 타입으로만 작성하였는데 링크 걸린 블로그 내용엔 aes128/256, ecb, cbc 타입으로 테스트 코드를 작성해 두었다.

# 암복호화 시리즈 

https://hjho95.tistory.com/25 Encrypt - AES128/256 ECB and CBC(with random iv) 테스트 코드
https://hjho95.tistory.com/26 Encrypt - AES128 CBC(with iv) example code
https://hjho95.tistory.com/27 Encrypt - SHA256(with salt) MessageDigest example code

# 암복호화 시리즈 참조 페이지

aes example: https://aesencryption.net/
cbc vs ecb: https://yoda.wiki/wiki/Block_cipher_mode_of_operation
sha example: http://wiki.hash.kr/index.php/SHA256#cite_note-8