스프링 부트

[SpringBoot] Encrypt - SHA256(with salt) MessageDigest example code

h__hj 2022. 10. 3. 19:11

# Encrypt - SHA256(with salt) MessageDigest example code

스프링 부트로 작성해보는 MessageDigest 를 이용한 sha-256 암호화 (+ salt) example code

# 참조

SHA-256
SHA(Secure Hash Algorithm)  알고리즘의 한 종류로서 256비트로 구성되며 64자리 문자열을 반환한다.
SHA-256은 미국의 국립표준기술연구소 (NIST; National Institute of Standards and Technology)에 의해 공표된 표준 해시 알고리즘인 SHA-2 계열 중 하나이며 블록체인에서 가장 많이 채택하여 사용하고 있다.

# 환경

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

 

# PROPERTIES FILE

## CRYPTO
crypto.sha256.algorithm = SHA-256
crypto.sha256.salt      = ENC(5IV49eD42Ftq6aTb9BRLqnW6s00M2KsK0cNFbkduWdQKc79le17/6wyHR9W+qi0R)

# Configuration

@Configuration
public class CryptoConfig {
	
    public final static String DEFAULT_CHAR_SET = "UTF-8";
	
    @Bean("sha256Encryptor")
    public MessageDigest sha256Encryptor(@Value("${crypto.sha256.algorithm}") String algorithm, @Value("${crypto.sha256.salt}") String salt) {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            md.update(salt.getBytes(DEFAULT_CHAR_SET));
            return md;
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            e.printStackTrace();
        } 
        return null;
    }
}

# Service

@Service
public class EncryptService {
	
    @Autowired
    private MessageDigest sha256Encryptor;
	
    /**
     * SHA256 ENCRYPT : length 64 
     */
    public String encryptSha256(String password, String charSet) {
        String encryptStr = "";
        try {
            byte[] encryptBytes = sha256Encryptor.digest(password.getBytes());
            encryptStr = EncryptService.byteArrayToHexString(encryptBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptStr;
    }
    /**
     * SHA256 ENCRYPT EQUAL CHECK
     */
    public boolean checkSha256(String password, String charSet, String encPassword)  {
        String encrypt = this.encryptSha256(password, charSet);
        if(encrypt.equals(encPassword)) {
            return true;
        }
        return false;
    }
    
    public static String byteArrayToHexString(byte[] bytes) {
        if(bytes == null || bytes.length == 0) {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        for (byte b: bytes) {
            builder.append(String.format("%02X", b));
        }
        return builder.toString();
    }
}

# Controller

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

    @Autowired
    private EncryptService encryptService;
	
    @PostMapping("/sha/encrypt")
    @ResponseBody
    public Map<String, String> shaEncrypt(@RequestParam String password) {
        log.debug("[PARAMETER] {}", password);
		
        String encPassword = encryptService.encryptSha256(password, CryptoConfig.DEFAULT_CHAR_SET);
		
        Map<String, String> response = new HashMap<String, String>();
        response.put("password", password);
        response.put("encPassword", encPassword);
        return response;
    }
	
    @PostMapping("/sha/check")
    @ResponseBody
    public Map<String, Boolean> shaCheck(@RequestParam String password, @RequestParam String encPassword) {
        log.debug("[PARAMETER] {}, {}", password, encPassword);
		
        boolean isEqual = encryptService.checkSha256(password, CryptoConfig.DEFAULT_CHAR_SET, encPassword);
		
        Map<String, Boolean> response = new HashMap<String, Boolean>();
        response.put("isEqual", isEqual);
        return response;
    }	
}

# 페이지

# HTML

<div>
    <h3>SHA 256 MessageDigetst</h3>
    <input type="text" id="password" placeholder="Encrypt Password" style="display: inline;"/>
    <input type="text" id="encPassword" placeholder="Password Check" style="display: inline;"/>
    <br>
    <input type="button" onclick="test.shaEncrypt()" value="ENCRYPT" style="display: inline;"/>
    <input type="button" onclick="test.shaCheck()" value="CHECK" style="display: inline;"/>
</div>

# javascript (jQuery)

const test = {
    shaEncrypt: function() {
        const input = {
            password: $('#password').val()
        }
        AjaxUtils.sendPost(`/test/encrypt/sha/encrypt`, input, 
            function(result) {
                console.log("result:", result.encPassword);
                $('#encPassword').val(result.encPassword);
            }
        );
    },
    shaCheck: function() {
        const input = {
            password: $('#password').val(),
            encPassword: $('#encPassword').val()
        }
        AjaxUtils.sendPost(`/test/encrypt/sha/check`, input, 
            function(result) {
                console.log("result:", result.isEqual);
            }
        );
    },
}

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

# update 와 digest

  • 위키의 example code는 update에서 message를 입력하고 digest로 암호화 하지만,
  • 블로그 본문에선 update는 salt를 digest에서 message를 입력하고 암호화한다.

함수 doc을 번역하면 아래와 같다.

  • update: 지정된 바이트 배열을 사용하여 요약을 업데이트합니다.
  • digest: 요약이 완료되기 전에 업데이트할 입력을 입력합니다.

따라서, 순서의 차이는 있겠지만

  • update(salt) > update(data) > digest()
  • update(salt) > digest(data) 
  • 위 두개는 같은 값이 나오고, 
  • update(data) > digest(salt) 는 다른 값이 나옴.

# 암복호화 시리즈 

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