고딩왕 코범석

Jasypt 적용하기 + 이슈 본문

Language & Framework/Spring

Jasypt 적용하기 + 이슈

고딩왕 코범석 2022. 1. 22. 23:50
반응형

안녕하세요! 이번 포스팅은 제가 jasypt를 사용하고 겪은 이슈에 대해 공유드리려 합니다.

Jasypt?

Jasypt는 홈페이지에서 다음과 같이 설명합니다.

Jasypt is a java library which allows the developer to add basic encryption capabilities to his/her projects with minimum effort, and without the need of having deep knowledge on how cryptography works.

직역해보면 개발자들의 프로젝트를 기본적으로 암호화 해줄수있게 해주고 보안적인 지식이 깊지 않더라도 쉽게 적용할 수 있는 자바 라이브러리 라고 합니다.

사용 방법

우선 build.gradle에 다음과 같이 의존성을 추가해줍니다.

implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4'

그 후 Configuration을 다음과 같이 등록해줍니다.

@Configuration
@EnableEncryptableProperties
public class JasyptConfig {

    @Value("${jasypt.encryptor.password}")
    private String encryptKey;

    @Bean("jasyptStringEncryptor")
    public StringEncryptor stringEncryptor() {
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(encryptKey);
        config.setAlgorithm("PBEWithMD5AndDES");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        return encryptor;
    }
}

@Value를 통해 yml에서 주입받음을 알 수 있지만, encryptKey를 application.yml 에 등록하면 암호화를 안하는 것과 마찬가지입니다.

그래서 jar 실행 시 -Djasypt.encryptor.passowrd={직접 키 입력} 을 통해 주입해줘야 합니다.

적용을 하기 전에 yml 설정부터 짚어보겠습니다.

jasypt:
  encryptor:
    bean: jasyptStringEncryptor
    property:
      prefix: ENC(
      suffix: )

jasypt.encryptor.bean

이곳에서는 @Bean으로 등록한 StringEncryptor의 Bean name을 넣어줍니다.

jasypt.encryptor.bean.property

jasypt가 인식할 수 있게끔 prefix는 ENC(, suffix는 )를 넣어줍니다.

암호화를 주로 사용하는 곳은 애플리케이션의 DB 연결시 필요한 username, password나 외부 라이브러리나 api 연결 시 필요한 정보를 yml에 표기할 때 입니다. 그렇다면 암호화된 값을 어떻게 가져올까요?

저는 테스트 코드를 통해 가져왔습니다. 코드를 보면 바로 이해되실겁니다.


public class JasyptTest {

    @Test
    void test() {
        // given
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword("test");
        config.setAlgorithm("PBEWithMD5AndDES");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);

        // when
        String testString = "testString";
        String encryptedTestString = encryptor.encrypt(testString);
        System.out.println(encryptedTestString);

        // then
        assertEquals(testString, encryptor.decrypt(encryptedTestString));
    }
}

given절에는 설정과 똑같은 코드를 가져왔습니다. when절에는 testString을 암호화 후, 이를 복호화한 값이 원문과 맞는지 then절에서 검증했습니다.

image

print한 암호화문을 검증해보겠습니다.

@Test
    void test() {
        ...

        // when
        String testString = "testString";

        // then
        assertEquals(testString, encryptor.decrypt("Z8ICfwBWU3COACibH7CF5iOFg4A0Sxrh"));
    }

image

이렇게 해서 암호화가 잘 되었음을 알 수 있습니다. 이제 암호화된 문자열을 앞에 prefix, suffix에서 설정했던 것 처럼 넣어줍니다.

cloud:
  aws:
    credentials:
      access-key: ENC(vddcKGLGK7uW5FuMCppk/9KrEpqOLD1bv9MwgGcriQE=)
      secret-key: ENC(MmqBJCNvh4dUz7oxFIxYhd+i4LUjfXs0KZwOz9+Vfco2WkXU4/boAkG9iwoP1xgjTkhRM3G2fC0=)

이런식으로요. 참고로 이 yml은 제가 진행하고있는 챌린지에서 사진 저장을 위해 S3와 연동하기 위한 access-key, secret-key를 암호화한 설정입니다.

이슈 상황 (S3 연동)

평소같았으면 예시를 데이터베이스 username, password로 보여드렸을 것입니다.
근데 왜 굳이 S3 연동하는 yml 설정값으로 보여드렸을까요?

우선, 저는 S3를 연동할 때 별도의 Configuration 클래스를 만들지 않고 디폴트(SpringBoot의 Autoconfiguration)로 설정했습니다. 하지만 이번 프로젝트에서는 jasypt를 적용했을 때 AutoConfiguration이 병목이 될 줄 몰랐습니다. 같이 보시죠

image

우선, 기본 설정의 경우에는 애플리케이션을 돌리는데 문제가 없습니다. 문제는 실제 사진 저장(S3를 사용할 때)할 때 입니다.

com.amazonaws.services.s3.model.AmazonS3Exception: The authorization header is malformed; the authorization component "Credential=ENC(vddcKGLGK7uW5FuMCppk/9KrEpqOLD1bv9MwgGcriQE=)/20220122/ap-northeast-2/s3/aws4_request" is malformed. (Service: Amazon S3; Status Code: 400; Error Code: AuthorizationHeaderMalformed; Request ID: 9J3F6501ZENBB3NY; S3 Extended Request ID: TmqLaB/k8P2xcdlzZvnDnVwytbSBzAvJOn/nMXTDvk4qeXrH3HE+ku1SiEKyzR//n/I5+/JB81k=; Proxy: null)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1811) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleServiceErrorResponse(AmazonHttpClient.java:1395) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1371) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1145) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:802) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:770) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:744) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:704) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:686) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:550) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:530) ~[aws-java-sdk-core-1.11.792.jar:na]
    at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5062) ~[aws-java-sdk-s3-1.11.792.jar:na]
    at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5008) ~[aws-java-sdk-s3-1.11.792.jar:na]
    at com.amazonaws.services.s3.AmazonS3Client.access$300(AmazonS3Client.java:394) ~[aws-java-sdk-s3-1.11.792.jar:na]
    at com.amazonaws.services.s3.AmazonS3Client$PutObjectStrategy.invokeServiceCall(AmazonS3Client.java:5950) ~[aws-java-sdk-s3-1.11.792.jar:na]
    at com.amazonaws.services.s3.AmazonS3Client.uploadObject(AmazonS3Client.java:1812) ~[aws-java-sdk-s3-1.11.792.jar:na]
    at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1772) ~[aws-java-sdk-s3-1.11.792.jar:na]
    at com.danggn.challenge.common.client.S3StorageClient.upload(S3StorageClient.java:21) ~[classes/:na]

The authorization header is malformed; 라는 메세지와 함께 Credential 값을 복호화시킨 값이 아닌 yml에 적어둔 ENC(...) 그대로 읽어온 것입니다.

그래서 디버깅 모드로 애플리케이션을 띄울 때 BasicAWSCredentials 클래스와 앞에서 설정한 JasyptConfig 의 생성자에 디버깅을 찍어보니 BasicAWSCredentials 클래스가 먼저 빈 주입이 되었습니다.

그래서 AWS 설정 Configuration 클래스를 따로 만든 후 디버깅 포인트를 걸어보았습니다. 클래스는 다음과 같습니다.

@Configuration
public class AmazonS3Config {

    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;

    @Value("${cloud.aws.region.static}")
    private String region;

    @Bean
    public AmazonS3Client amazonS3Client() {
        BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
        return (AmazonS3Client) AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .build();
    }
}

이렇게 AmazonS3Config를 만든 후에 BasicAWSCredentials, JasyptConfig, AmazonS3Config 생성자에 모두 디버깅 포인트를 걸어 run 해보면

image

우선 똑같이 BasicAWSCredentials가 디버깅 포인트에 걸립니다. 그 다음은

image

이렇게 JasyptConfig가 잡힙니다. 마지막으로

image

이렇게 AmazonS3Config가 잡힙니다. 그러고 나서 이 암호화된 값을 다시

image

BasicAWSCredentials 클래스에 덮어씌웁니다. 이렇게 해서 복호화된 Credential이 주입됨이 확인되었습니다.

반응형