본문 바로가기
자바

암복호화

by 베어그릴스 2022. 7. 29.
반응형

암호화란?

- 중요한 자료를 알골리즘을 이용하여 암호문으로 변환하는 과정이다.

ex) 불라불라 -> 암호화 -> 암호화("asdsad#dsfsdf!#@#@$(")

 

복호화란?

- 암호문을 원래 데이터로 변환하는 과정이다.

ex)암호문

위 암호화한 텍스트를 asdsad#dsfsdf!#@#@$( -> 복호화 -> 평문 : 불라불라

 

암호는 단방향, 양방향으로 나뉜다.

단방향 : 재사용 X (Ex.사용자 비밀번호) 

양방향 : 재사용 O (Ex.주소,이메일)단방향 

암호는 bcrypt가 제일 보편화 되어있다.

양방향 암호는 AES 와 RSA 등을 쓴다 (선택사항)암호의 알고리즘을 직접 구현할 수 있다.

반응형

용어설명

SHA Secure Hash Algorithm

SHA-1 ver.1을 뜻함 160비트 은 2008년에 보안에 심각한 결함이 발견

SHA-2 SHA-224, SHA-256,SHA-384, SHA-512가 만들어 졌다. ( 뒤에 숫자는 비트수를 의미한다. )

 

MD5 Message-Digest algorithm 5

128비트 암호화 해시 함수이다

1996년에 MD5의 설계상 결함이 발견되었다.

이것은 매우 치명적인 결함은 아니었지만, 암호학자들은 해시 용도로 SHA-1과 같이 다른 안전한 알고리즘을 사용할 것을 권장하기 시작했다.

2004년에는 더욱 심한 암호화 결함이 발견되었고. 

2006년에는 노트북 컴퓨터 한 대의 계산 능력으로 1분 내에 해시 충돌을 찾을 정도로 빠른 알고리즘이 발표되기도 하였다.

현재는 MD5 알고리즘을 보안 관련 용도로 쓰는 것은 권장하지 않으며, 심각한 보안 문제를 야기할 수도 있다.

2008년 12월에는 MD5의 결함을 이용해 SSL 인증서를 변조하는 것이 가능하다는 것이 발표되었다.

 

상세설명

Validation

1.동일한 메시지를 계산하면 같은 값이 나오는 로직으로 MD5, Sha1, Sha2 등의 알고리즘이 쓰이다. 아무리 크기가큰 String값(소설책 100권 분량이상)을 계산해도 모두 동일한 크기의 계산값이 나온다 자세한 알고리즘은 틀리나 원리는 소설책에 들어간 단어를 수와 공백의 수 그리고 부호의 수 등을 더한 값이 같다면 같은 내용의 소설책일것이다. 2.그러나 계산된 값으로 소설책의 내용을 복원하는 것은 불가능 하다. 메시지가 서로 같은가는 증명하나 내용의 복원은 불가능 하다. 3.이런 특징 때문에 초기에 패스워드를 저장하는 수단으로도 많이 사용됐다. 패스워드가 암호화가 되어 있더라도 복원이 가능하면 관리자가 임의로 패스워드를 알수 있으므로 패스워드 같은 경우 복호화되는 로직으로 관리하면 절대 안된다.4.배포하는 프로그램등이 변조되지 않았다는 증명을 할수 있다.

 

소스코드 PassWordManager.java

    public static String getCheckSum(String message)
    {
        return DigestUtils.sha256Hex(message.getBytes());
    }

테스트는 아래와 같이 같은 값을 가지는 고유한 값이 만들어진다.

        String msg = "생존왕만세!";

        for(int i = 0; i < 5; i++)
        {
            System.out.println(PassWordManager.getCheckSum(msg));
        }
더보기

27805d1247627a460e6252fd8ed67bd617b6d89de91bd5e0c2fd3893629265b3
27805d1247627a460e6252fd8ed67bd617b6d89de91bd5e0c2fd3893629265b3
27805d1247627a460e6252fd8ed67bd617b6d89de91bd5e0c2fd3893629265b3
27805d1247627a460e6252fd8ed67bd617b6d89de91bd5e0c2fd3893629265b3
27805d1247627a460e6252fd8ed67bd617b6d89de91bd5e0c2fd3893629265b3

Hmac

 

1. 아래와 같이 보내는 사람과 받는 사람이 같은 알고리즘 같은 key를 사용해서 계산을 해야 같은 값이 만들어진다.

    public static String getHmac(String key, String message)
    {
        try
        {
            Mac           sha256HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey  = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256HMAC.init(secretKey);

            return new String(Hex.encodeHex(sha256HMAC.doFinal(message.getBytes(StandardCharsets.UTF_8))));
        }
        catch(NoSuchAlgorithmException e)
        {
            CommonUtil.printException(e);
        }
        catch(InvalidKeyException e)
        {
            CommonUtil.printException(e);
        }

        return null;
    }

테스트

    public static void main(String[] args) throws Exception
    {
        String key  = "OriginalKey";
        String msg  = StringUtil.getRandomString(40);
        String hmac = PassWordManager.getHmac(key, msg);
        CommonUtil.print(key, msg, hmac);

        key = "ModifyKey";
        CommonUtil.print(key, msg, PassWordManager.getHmac(key, msg)); // 키가 다르면 값이 다르게나옴
    }

 

같은 메시지라도 키가 틀리면 다른 hash값이 나온다.

------------------------------ { Objects size:3 } ------------------------------
    OriginalKey
    hb5lGh86MIPm94Nek5rBzO1r0SRSfpPlCuelUZ71
    f330d392d19727e1452077ec0464b531fc348dc143ee0c7d19563eefc83730cb
-------------------------------- { End Objects } -------------------------------

------------------------------ { Objects size:3 } ------------------------------
    ModifyKey
    hb5lGh86MIPm94Nek5rBzO1r0SRSfpPlCuelUZ71
    765a5066741c8304784801501900c72ea5cc5cb464971e39d0bd24b18cbbb7fc
-------------------------------- { End Objects } -------------------------------

 

Password

1. 같은 값을 계산해도 계산 할때 마다 값이 틀려 지는것을 알수 있다. 그러나 계산된 값을 인수로 주고 계산시 같은 값을 계산하면 인수로전달된 계산값과 동일한 값이 얻어진다

    public static String getCryptPassword(String passwd)
    {
        if(StringUtil.isEmpty(passwd))
        {
            return null;
        }

        // DB 유출시 일반적인 사전으로 해독이 안되게 앞에 글자를 붙임
        passwd = EnvProperties.ENCRYPT_TOKEN + passwd;

        return Sha2Crypt.sha256Crypt(passwd.getBytes(StandardCharsets.UTF_8));
    }

패스워드검증

public static boolean checkPassword(String passwd, String cryptedPwd)
{
        if(StringUtil.isEmpty(passwd))
        {
            return false;
        }

        if(StringUtil.isEmpty(cryptedPwd))
        {
            return false;
        }

        // DB 유출시 일반적인 사전으로 해독이 안되게 앞에 글자를 붙임
        passwd = EnvProperties.ENCRYPT_TOKEN + passwd;

        return cryptedPwd.equals(Sha2Crypt.sha256Crypt(passwd.getBytes(StandardCharsets.UTF_8), cryptedPwd));

}

테스트

        String passwd = "생존왕@만세!";
        String cryptedPwd = "";

        for(int i = 0; i < 2; i++)
        {
            cryptedPwd = PassWordManager.getCryptPassword(passwd);
            System.out.println(passwd);
            System.out.println(cryptedPwd);
        }

        System.out.println(PassWordManager.checkPassword(passwd, cryptedPwd));
더보기

생존왕@만세!
$5$3LObJ70p$4Ad8W0sTSv54mXFftxdMrJSkvk7L/eruxFZ4YtzPjt7
생존왕@만세!
$5$KMxnbDKe$e7IFan03ZRVka7B3L.PxBNnY4m6r.wo45WY0AC7D7A.
true

대칭키

1. 암복호화를 같은 키로 함 암호화 할때 암호화한 시간을 넣어서 체크시 만료시간을 초과한 암호문에 대해서는 복호화 하지 않는 로직을 넣었다

 

소스 SecurityUtil.java

    public static final String encrypt(String sKey, String sText)
    {
        if(StringUtil.isEmpty(sText))
        {
            return null;
        }

        long curTime = System.currentTimeMillis();

        sText = curTime + sText;

        return EnvProperties.ENCRYPT_TOKEN + encryptAesGcm(sKey, sText);
    }
    public static final String decrypt(String sKey, String sText, long expMillisecond)
    {
        if(StringUtil.isEmpty(sText))
        {
            return null;
        }

        // 암호화토큰이 앞자리 없으면 받은 문자 그대로 돌려줌
        if(sText.startsWith(EnvProperties.ENCRYPT_TOKEN))
        {
            sText = sText.substring(EnvProperties.ENCRYPT_TOKEN.length());
        }
        else
        {
            return sText;
        }

        String decStr  = decryptAesGcm(sKey, sText);

        if(StringUtil.isEmpty(decStr))
        {
            return sText;
        }

        if(decStr.length() < 12)
        {
            return sText;
        }

        //암호화 된지 exprSec초이상이면 버린다.
        long encTime = Long.parseLong(decStr.substring(0, 13));
        long curTime = System.currentTimeMillis();

        if(curTime - encTime > expMillisecond)
        {
            CommonUtil.printErr("The encrypted sentence has expired.", expMillisecond, DateUtil.procTime(encTime, curTime));
            return null;
        }

        return decStr.substring(13); // 시간을 제외한 실제문장 리턴
    }

테스트

    public static void main(String[] args) throws Exception
    {
        String secKey      = "생존왕!";
        String checkKeyVal = "만세";
        String encCheckVal = SecurityUtil.encrypt(secKey, checkKeyVal);


        // 아래가 통과 되면 즉 checkKeyVal 값과 같으면 됨
        CommonUtil.print(encCheckVal, SecurityUtil.decrypt(secKey, encCheckVal));
    }
 /*----------------------------------------------------------------------------------------------------------------------
  * [DEBUG : 2022-07-29 16:40:06.300]
  *--------------------------------------------------------------------------------------------------------------------*/

------------------------------ { Objects size:2 } ------------------------------
    ┫┫┏vjD3LEq471bgWr522gIG4KoUoXRwpU4njUxEysv2L/41UKU=
    만세
-------------------------------- { End Objects } -------------------------------

비대칭키

1. 암호화하는 키와 복호화하는 키가 틀린경우이다. 가장 많이 사용하는 패턴

    public static final byte[] encrypRsa(PrivateKey sKey, byte[] bytes)
    {
        return cipherWork(RSA, Cipher.ENCRYPT_MODE, sKey, bytes);
    }

    public static final String encrypRsa(PrivateKey sKey, String inStr)
    {
        return cipherWork(RSA, Cipher.ENCRYPT_MODE, sKey, inStr);
    }

    public static final byte[] encrypRsa(PublicKey sKey, byte[] bytes)
    {
        return cipherWork(RSA, Cipher.ENCRYPT_MODE, sKey, bytes);
    }

    public static final String encrypRsa(PublicKey sKey, String inStr)
    {
        return cipherWork(RSA, Cipher.ENCRYPT_MODE, sKey, inStr);
    }

테스트

    public static void main(String[] args) throws Exception
    {
        String inputStr = "생존왕만세!";

        KeyPair keyPair = RsaUtil.getRsaKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        String encStr = SecurityUtil.encrypRsa(publicKey, inputStr);
        CommonUtil.print("encrypRsa", encStr);


        String decStr = SecurityUtil.decryptRsa(privateKey, encStr);
        CommonUtil.print("decryptRsa", decStr);
    }
더보기

------------------------------ { Objects size:2 } ------------------------------
    encrypRsa
    a5AH8cycjHQ4pTTJKGNfQxYCWGVm3Gtpy30jdpJs7yTpriRBVoCfAbexAGYpM0oks/4qYbHSWhRas871PciGZDb0byWCTbKyDxiQQ9YIDuJWDzkS07k/JrkFzFljAWu+5RZ539b9LfphteCf6ouHvkZELn+8hA49B4z4kbZCzBBo0tEhsHaKGB0M2/nJM1ifWJFt8lP7BX2XpqPOykTXqeLP2x5b6wUxyPI1GMel3xDZieg7Sh31Z7uAa3ZsFa2b+Zjp1juCixhfV+D9JMzMS0NGyuUNs2tKmJY5b+3K7GJmNTH2TURAFEzI50EiP6FdrKim8NFpugJNqRQVstyMpA==
-------------------------------- { End Objects } -------------------------------
------------------------------ { Objects size:2 } ------------------------------
    decryptRsa
    생존왕만세!
-------------------------------- { End Objects } -------------------------------

 

반응형

댓글