티스토리 뷰

Spring Security의 Crypto 모듈은 대칭키 암호화와 키 생성, 패스워드 인코딩 기능을 제공합니다. 그 중 대칭키 암호화에 관한 인터페이스인 BytesEncryptorTextEncryptor를 소개합니다.

BytesEncryptor

BytesEncryptor를 사용하면 인코딩한 결과물의 형식이 byte[]가 됩니다. org.springframework.security.crypto.encrypt.Encryptors 클래스에는 BytesEncryptor구현체의 인스턴스를 생성하는 두 가지 메서드가 정의되어 있습니다.

  • static BytesEncryptor stronger(java.lang.CharSequence password, java.lang.CharSequence salt)
  • static BytesEncryptor standard(java.lang.CharSequence password, java.lang.CharSequence salt)

두 메서드의 차이는 싸이퍼 알고리즘의 모드입니다. stronger 메서드는 GCM을 사용하며 standard는 CBC를 사용합니다. 인터넷에서 쉽게 찾을 수 있는 예제는 대부분 CBC를 사용하지만 스프링에서 권장하는 방식은 GCM을 사용하는 stronger 메서드입니다.

BytesEncryptor encryptor = Encryptors.stronger("This is password!", "0123456f");
String rawText = "This is text!";
byte[] cipherBytes = encryptor.encrypt(rawText.getBytes(StandardCharsets.UTF_8));

BytesEncryptor를 생성하는 팩토리 메서드입니다. 앞에 들어가는 password 패러미터는 암호키를 생성하기 위해 필요한 암호입니다. 뒤에는 salt인데 hex(16진수) 형식으로 인코딩해야 합니다. 만약 위에서 입력한 0123456f가 0123456g로 바뀌면 범위를 넘어가는 문자인 g 때문에 실행할 때 에러가 발생합니다. password와 salt를 올바르게 입력하면 이를 이용해 비밀키를 생성합니다.

TextEncryptor

TextEncryptor를 사용하면 인코딩한 결과물의 형식이 hex로 인코딩된 스트링이 됩니다. Encryptors 클래스에는 TextEncryptor구현체의 인스턴스를 생성하는 네 가지 메서드가 정의되어 있습니다.

  • static TextEncryptor delux​(java.lang.CharSequence password, java.lang.CharSequence salt)
  • static TextEncryptor noOpText()
  • static TextEncryptor queryableText​(java.lang.CharSequence password, java.lang.CharSequence salt)
  • static TextEncryptor text(java.lang.CharSequence password, java.lang.CharSequence salt)

queryTableText(...) 메서드 같은 경우는 초기화 벡터(이하 iv)가 항상 일정하기 때문에(0 값이 16 바이트로 채워진) 동일한 문자열을 인코딩할 땐 몇 번을 수행해도 인코딩 결과가 똑같습니다. 안정성 문제로 deprecated 처리됐으니 사용을 자제하는 것이 좋습니다. noOpText() 메서드로 생성한 인스턴스는 암호화 자체를 하지 않기 때문에 활용도가 높지 않습니다. 남는 건 BytesEncryptor인터페이스처럼 두 가지 메서드밖에 없습니다. delux(...) 메서드와 text(...) 메서드인데 delux는 stronger 메서드를, text는 standard 메서드를 내부적으로 호출합니다.

TextEncryptor encryptor = Encryptors.delux("This is password!", "0123456f");
String rawText = "This is text!";
String cipherText = encryptor.encrypt(rawText);

delux 메서드의 사용 예시입니다. 암호화 메서드의 패러미터 타입과 반환 값 타입이 byte[]가 아닌 String입니다.

BytesEncryptor의 구현

Spring에서 BytesEncryptor를 어떤 식으로 구현했는지 Encryptors.stronger(java.lang.CharSequence,java.lang.CharSequence) 메서드의 소스를 분석 후 암호화 메서드와 복호화 메서드를 단순화시켜서 보여드리겠습니다.

String password = "test1234";
String salt = "01234567";

BytesKeyGenerator ivGenerator = KeyGenerators.secureRandom(16);
SecretKey secretKey = new SecretKeySpec(
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
                .generateSecret(new PBEKeySpec(password.toCharArray(), Hex.decode(salt), 1024, 256))
                .getEncoded(), "AES");

Cipher encryptor = Cipher.getInstance(AesBytesEncryptor.CipherAlgorithm.GCM.toString());
Cipher decryptor = Cipher.getInstance(AesBytesEncryptor.CipherAlgorithm.GCM.toString());

String rawText = "테스트 문자열";

byte[] iv = ivGenerator.generateKey();
encryptor.init(Cipher.ENCRYPT_MODE, secretKey, AesBytesEncryptor.CipherAlgorithm.GCM.getParameterSpec(iv));
byte[] encrypted = EncodingUtils.concatenate(iv, encryptor.doFinal(rawText.getBytes(StandardCharsets.UTF_8)));

decryptor.init(Cipher.DECRYPT_MODE, secretKey, AesBytesEncryptor.CipherAlgorithm.GCM.getParameterSpec(iv));
iv = EncodingUtils.subArray(encrypted, 0, ivGenerator.getKeyLength());
byte[] decrypted = decryptor.doFinal(EncodingUtils.subArray(encrypted, iv.length, encrypted.length));

Assert.assertEquals(rawText, new String(decrypted, StandardCharsets.UTF_8));

실제 소스는 AesBytesEncryptor, EncodingUtils, CipherUtils 등의 클래스를 활용하며, 암호화와 복호화 부분에 synchronized 블럭이 들어가지만 모두 생략하고 한 소스에서 파악할 수 있도록 했습니다. Encryptors 의 팩토리 메서드는 password, salt만 패러미터로 이용합니다. iv는 내부적으로 생성하고 API 사용자에게 노출하지 않습니다. iv 프로퍼티에 접근할 수 있는 메서드가 없는데 복호화는 어떻게 할까요? 암호화 메서드 호출시 위의 소스처럼 iv 값이 암호화 값 앞에 붙어서 반환되기 때문에 문제가 없습니다. iv 길이도 16바이트로 정해져있기 때문에 복호화 할 때도 암호화된 값에서 iv만큼 잘라서 iv로 이용하고 나머지 부분을 복호화합니다.

지금까지 BytesEncryptor와 TextEncryptor에 대해서 알아봤습니다. Spring 기반 프로젝트에서 AES-256을 이용한 암호화를 구현해야 한다면 직접 구현하기보단 BytesEncryptor또는 TextEncryptor 인터페이스를 써보는 건 어떨까요?

참조

Spring Security Crypto Module

org.springframework.security.crypto.encrypt.Encryptors

'Spring Framework' 카테고리의 다른 글

JavaMailSender로 메일 보내기  (0) 2022.04.22
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글