[E-commerce App] 설정 정보의 암호화 처리
application.yml
혹은 bootstrap.yml
에 있는 plain text 값들을 암호화처리하는 방법에 대해 알아보자.
개요
대칭키 (Shared)
- 암/복호화 시에 같은 키를 사용한다.
비대칭키 (RSA Keypair)
- 암호화 할 때 사용되는 키와 복호화 할 때 사용되는 키가 다르다.
- 공개키와 사설키를 사용한다.
- 키 값을 생성하기 위해 Java keytool을 사용한다.
흐름
민감한 환경 변수가 담긴 application.yml
파일을 그대로 git repository에 올리게되면, 보안적인 이슈가 발생할 수 있으므로 암호화를 진행할 것이다.
이 때 텍스트가 암호화 되어있다는 표시로, 텍스트 앞에 {cipher}
를 붙인다.
암호화 된 값이 Spring Cloud Coonfig Server에서 읽혀지고,
각각의 마이크로 서비스들에게 전달되는 시점에, key를 이용해 cipher text를 복호화 한 뒤에 사용한다.
이렇게 repository 혹은 파일시스템에 민감한 데이터 혹은 secret 값을 저장시켜 놓는다고 하더라도 외부에 노출될 수 있는 위험을 최소화 할 수 있다.
JCE (Java Cryptography Extension)
java8(jdk8)을 사용한다면 JCE를 추가적으로 사용해야한다.
🚨 주의
- java8(jdk8)을 사용한다면, encryption 혹은 decryption 할 때 아래와 같은 문제가 발생할 수 있다.
- Illegal key size or default parameters
- Unsupported keysize of algorithm parameters- 이 경우에는 아래 사이트에서 필요한 파일을 추가로 설치해야 한다.
- https://www.oracle.com/java/technologies/javase-jce-all-downloads.html- 이 과정은 java11(jdk11) 이상을 사용할 때에는 생략할 수 있다.
JCE를 사용해야한다고 하면, 위에서 말한 파일을 다운로드 받은 후에 아래 과정을 따라하자.
대칭키를 이용한 암호화
🍊 config-service
pom.xml
bootstrap이 이미 추가되어있다면 추가하지 않아도 된다.
<!-- Bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
bootstrap.yml
기존에 bootstrap.yml
파일이 없었기 때문에 새로 생성한다.
대칭키 방식은 암/복호화 시 같은 키를 사용한다.
이때 사용될 키를 bootstrap.yml
에 정의하자. (키는 마음대로 정의하면 된다.)
encrypt:
key: abcdefghijklmnopqrstuvwxyz0123456789
테스트
암호화가 잘 되는지 테스트를 해보자.
service-discovery
와 방금 설정한config-service
두 서버를 기동한 뒤에,
포스트맨에서http://localhost:8888/encrypt
에 다음 요청을 해보자.
암호화가 되었다.
이번에는 cipher text를 가지고 복호화 테스트를 해보자.
http://localhost:8888/decrypt
에 다음 요청을 해보자.
복호화도 잘 되는 것을 확인할 수 있다.
🍊 user-service
application.yml
application.yml
에 있던 db 연동 시 필요한 설정 정보를 별도의 파일로 따로 분리시키자.
이는 config-service
의 user-service.yml
로 이동시킬 것이다.
아래에서는 어떤 정보가 사리졌는지 나타내기 위해 주석으로 표시했지만, 실제로는 삭제한다.
# datasource:
# driver-class-name: org.h2.Driver
# url: jdbc:h2:mem:testdb
# username: sa
# password: 1234
bootstrap.yml
읽어오고자 하는 설정 파일의 정보를 명시한다.
이 때 name
에 config-service
라는 Configuration Server의 이름을 적게되면 application.yml
파일을 읽어오고,
user-service
와 같은 특정 이름을 명시하면 user-service.yml
을 읽어온다.
spring:
cloud:
config:
uri: http://127.0.0.1:8888
name: user-service # 🌟 native-file-repo 안에 있는 user-service.yml 파일을 읽어온다.
🍊 native-file-repo
user-service.yml
아까 user-service
에서 잘라낸 db 연결 정보를 user-service.yml
로 붙여넣는다.
이 때, h2 database의 password(= 1234
)를 암호화해서 넣어준다.
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}c0c61dbe3eb9ef876f5b1810bd510a49d2ee8aa679956f68d9d1d4ff3b661108'
...
파일을 저장한 뒤에 config-service
를 재기동해준다.
참고
암호화는 위에서 했던 것처럼 포스트맨을 이용하면 된다.
테스트
테스트를 위해 gateway-service
도 기동해준다. (현재 service-discovery
-> config-service
-> gateway-service
-> user-service
가 기동되어있다.)
http://localhost:8888/user-service/default
에 접속해보자.
아래 사진에서 웹 브라우저를 통해 보이는 단계가 user
라는 마이크로 서비스가 사용하는 시점이라고 볼 수 있다.
spring.password
를 보면, 정상적으로 1234
라는 plain text를 읽어오는 것을 확인할 수 있다.
따라서 저장할 때에는 암호화된 cypher text를 저장하고, 사용할 때에는 위 사진에서 처럼 decrypted 된 plain text를 사용함을 알 수 있다.
만약 패스워드에 잘못된 암호문을 넣는다면?
native-file-repo/user-service.yml
에서 password에
'{cipher}c0c61dbe3eb9ef876f5b1810bd510a49d2ee8aa679956f68d9d1d4ff3b661108_wrong'
과 같이 일부러 잘못된 암호문을 넣어보자.
http://localhost:8888/user-service/default
페이지를 새로고침 하게 되면, 아래와 같이<n/a>
로 표시됨을 알 수 있다.
h2-console 접속 테스트
localhost:8761
으로 유레카 홈에 들어가서 user-service
의 포트번호를 확인한 뒤, 주소 마지막을 /h2-console
로 변경해 h2 콘솔로 접속한다.
이전에 했던 것처럼 패스워드 없이 test connection을 시도해보자.
패스워드 없이는 접속되지 않는 것을 확인했다.
이번에는 패스워드에 1234
를 입력해보자.
우리가 암호문으로 설정한대로 1234
를 입력해야 접속이 되는 것을 확인할 수 있다.
비대칭키를 이용한 암호화
키 생성
- public, private key 생성 -> JDK keytool 이용
- 보통은 암호화에 private key를 사용하고 복호화에 public key를 사용하지만 반대가 되어도 상관없다. (암/복호화 키가 서로 다르기만 하면)
# key가 저장될 디렉토리 생성
$ mkdir ${user.home}/study/msa/keystore
# keystore 디렉토리로 이동
$ cd keystore
# key 생성
$ keytool -genkeypair -alias apiEncryptionKey -keyalg RSA \
-dname "CN=Ann, OU=API Development, O=test.co.kr, L=Seoul, C=KR" \
-keypass "test1234" -keystore apiEncryptionKey.jks -storepass "test1234"
-alias
: 생성된 키에 대한 별칭, alias를 통해 호출하고 사용 (여기서는 생성하고자 하는 키 파일과 같은 형식으로 지정)-keyalg
: 키 생성 시, 사용되는 알고리즘-dname
: 키 생성 시, 셀프 인증에 사용되는 부가 정보
- CN: Company Name
- OU: Organization Unit
- O: Organization
- L: Location
- C: Country-keypass
: 키 생성 시, 패스워드 (임의로 생성)-keystore
: 키의 실제 파일 이름 (.jks
)-storepass
: 저장되는 키 파일의 패스워드 (임의로 생성)
생성된 키 정보를 자세히 확인해보고 싶다면, 아래 커맨드를 입력한다.
$ keytool -list -keystore apiEncryptionKey.jks -v
패스워드를 물어보면, 아까 지정한 test1234
를 입력한다.
그럼 위 사진과 같은 결과가 나오는데, PrivateKeyEntry
를 보면, 이 키는 private key 임을 알 수 있다.
그렇다면 이 private key에서 public key를 끄집어내보자.
$ keytool -export -alias apiEncryptionKey -keystore apiEncryptionKey.jks -rfc -file trustServer.cer
패스워드를 물어보면, 마찬가지로 test1234
를 입력한다.
성공적으로 공개키도 발급받았다.
그런데 .cer
확장자는 인증서 파일이다.
인증서 파일을 jks
파일로 변환하여 사용할 수도 있다.
$ keytool -import -alias trustServer -file trustServer.cer -keystore publicKey.jks
위 박스에는 키의 정보가 표시되고, 마지막에 예
를 입력하면 jks 파일로 변환할 수 있다.
이 키 정보를 아까처럼 자세히 확인해보자.
$ keytool -list -keystore publicKey.jks -v
trustedCertEntry
를 보면, 이 키는 public key 임을 알 수 있다.
참고
사실 Spring Cloud Config Server에서는 비밀키(apiEncryptionKey.jks
)만 사용할 것이다.
나머지는 나중에 공개키가 필요한 상황이 생긴다면, 사용해볼 수 있을 것이다.
🍊 config-service
이제 생성한 비밀키의 위치와 파일명을 Configuration Server에 명시하자.
bootstrap.yml
encrypt:
key-store:
location: file://${user.home}/study/msa/keystore/apiEncryptionKey.jks
password: test1234
alias: apiEncryptionKey
이제 config-service
를 재기동하자.
참고
어디까지가${user.home}
일까?
keystore 디렉토리에서pwd
를 입력하게되면 아래와 같이 나오는데,
여기서/Users/사용자명
까지가${user.home}
이다.
포스트맨 암호화 테스트
대칭키 때와 같은 방법으로 테스트한다.
대칭키 때보다 더 복잡하게 암호화가 되었다.
이번에는 cipher text를 가지고 복호화 테스트를 해보자.
복호화도 잘 되는 것을 확인할 수 있다.
🍊 native-file-repo
user-service.yml
아까 user-service
에서 잘라낸 db 연결 정보를 user-service.yml
로 붙여넣는다.
이 때, h2 database의 password(= 1234
)를 암호화해서 넣어준다.
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}AAQCgo+KT8kG9ISz3Rr0FEG2uoOR9jJXhjMzbTWgcDQtTh4rShUrj3KtiOfFaN8Gk/MoJeyszPKouvCrQihy1SyVjNiZuGaIXkk2bf4HTMayE4IP1xvLF7nOEHbvxuZR6dv2UUVK2eJcQ+bskIk9+fBjVL9C4t1Gw/VDA9rMVzu55VYcsdsRSEnIfSCUlOzaWaH0XsuiDbZ5UI1LxqEB3hXLkazCWkahj2jFoKElzZ1kZ/QRnOFQTtXrv3jtf0P0ytwTVBqEa5Gjz4JJLr3tybc7i0avs+tqXjWKj35C/3Aq+sDH1L3sl+JNns4QiDclTpHTt+wDg/pqlg2MhZ0HAOIJNBtuP5P/qk6Pwk0fCUyWhuifmOMo9OuBBZowXsMk0Hqg=' # 🌟 비대칭키 암호화 방식 사용
...
위 암호문은 기존 비밀번호인 1234
를 비대칭키 암호화 한 것이다.
파일을 저장한 뒤에 웹 브라어저에서 값을 확인해보자.
테스트
http://localhost:8888/user-service/default
에 접속해보자.
아까와 마찬가지로 정상적으로 1234
라는 plain text를 읽어오는 것을 확인할 수 있다.
🍊 gateway-service
지금까지는 db 정보를 암호화해서 사용했다면, 이번에는 토큰의 secret 정보를 암호화해서 사용해보자.
bootstrap.yml
현재 gateway-service
는 native-file-repo 안에 있는 ecommerce.yml 파일에서 구성 정보를 읽어오는 것을 확인할 수 있다.
spring:
cloud:
config:
uri: http://127.0.0.1:8888
name: ecommerce # 🌟 native-file-repo 안에 있는 ecommerce.yml 파일을 읽어온다.
🍊 native-file-repo
ecommerce.yml
기존 token.secret
에는 아래와 같이 plain text가 노출되어 있었다.
token:
expiration_time: 864000000
secret: user_token_native_ecommerce
gateway:
ip: 127.0.0.1
이를 아래처럼 암호문으로 변경하자.
token:
expiration_time: 864000000
secret: '{cipher}AQA4EotojUaSDNpQ5raB7YyPaZ+kOFdojsDz4UCo/cjHnAjQgZDrZFFvJxFPvYgNAUzQb2RJENdTsMai3WOhWxSJPWaWG/0l4qD3El0/eslslIfuvGMyrwTBaE97M05q/wRmjfBL/uW0pKmjAzNXX3NkGlVezq5HoNr+k9f4FEhz6CEdSnRsOndLkMr9AwOZFFzp1E/jCHdrQ80AZKdMPpCZLR8sSU/NZa28jL9odkKKGggJ6IFKTEBAIRbUwuHZX1jS+j9ugv+U6sD+QtzGEiNl0yoqoAeC52kfLJRsWS9JpA1HEJ0m+J2Bk4v7M37TtOXtPXJmsqEPuBNhLLuK48zN1nz9HKIqlMCc6J6Lr4v479l9c/73dtXNZNCgV609uzwWqTqa6/p1TiuH4NJszJyp'
gateway:
ip: 127.0.0.1
참고
암호화는 위에서 했던 것처럼 포스트맨을 이용하면 된다.
테스트
암/복호화 테스트
http://localhost:8888/ecommerce/default
에 접속해보자.
정상적으로 user_token_native_ecommerce
라는 plain text를 읽어오는 것을 확인할 수 있다.
gateway-service
기동 후 받아오는 토큰 값 확인
기존 application.yml
파일에 있던 토큰 정보를 주석처리 한다.
#token:
# secret: user_token
그리고 토큰 정보를 받아오는 곳에 break point를 찍고 디버깅 모드로 실행한다.
어떠한 user-service
를 호출해도 게이트웨이를 거치기 때문에,
http://localhost:8000/user-service/health-check
를 호출해보자.
암호문으로 등록한 token.secret
의 정보를 정상적으로 복호화하여 사용하는 것을 확인할 수 있다.
그런데, 중요한 것은 user-service
에서 사용하는 구성파일(native-repo-repository/user-service.yml
)에 있는 토큰 값과 gateway-service
에서 사용하는 구성파일(native-repo-repository/ecommerce.yml
)에 있는 토큰 값이 같아야 한다는 것이다.
따라서 user-service.yml
에 있는 토큰 정보를 ecommerce.yml
에 있는 토큰 정보와 일치시킨다.
user-service.yml
변경 전
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}AQCgo+KT8kG9ISz3Rr0FEG2uoOR9jJXhjMzbTWgcDQtTh4rShUrj3KtiOfFaN8Gk/MoJeyszPKouvCrQihy1SyVjNiZuGaIXkk2bf4HTMayE4IP1xvLF7nOEHbvxuZR6dv2UUVK2eJcQ+bskIk9+fBjVL9C4t1Gw/VDA9rMVzu55VYcsdsRSEnIfSCUlOzaWaH0XsuiDbZ5UI1LxqEB3hXLkazCWkahj2jFoKElzZ1kZ/QRnOFQTtXrv3jtf0P0ytwTVBqEa5Gjz4JJLr3tybc7i0avs+tqXjWKj35C/3Aq+sDH1L3sl+JNns4QiDclTpHTt+wDg/pqlg2MhZ0HAOIJNBtuP5P/qk6Pwk0fCUyWhuifmOMo9OuBBZowXsMk0Hqg='
token:
expiration_time: 864000000
secret: user_token_native_user_service # 토큰 정보 평문
gateway:
ip: 127.0.0.1
user-service.yml
변경 후
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb
username: sa
password: '{cipher}AQCgo+KT8kG9ISz3Rr0FEG2uoOR9jJXhjMzbTWgcDQtTh4rShUrj3KtiOfFaN8Gk/MoJeyszPKouvCrQihy1SyVjNiZuGaIXkk2bf4HTMayE4IP1xvLF7nOEHbvxuZR6dv2UUVK2eJcQ+bskIk9+fBjVL9C4t1Gw/VDA9rMVzu55VYcsdsRSEnIfSCUlOzaWaH0XsuiDbZ5UI1LxqEB3hXLkazCWkahj2jFoKElzZ1kZ/QRnOFQTtXrv3jtf0P0ytwTVBqEa5Gjz4JJLr3tybc7i0avs+tqXjWKj35C/3Aq+sDH1L3sl+JNns4QiDclTpHTt+wDg/pqlg2MhZ0HAOIJNBtuP5P/qk6Pwk0fCUyWhuifmOMo9OuBBZowXsMk0Hqg='
token:
expiration_time: 864000000
secret: '{cipher}AQA4EotojUaSDNpQ5raB7YyPaZ+kOFdojsDz4UCo/cjHnAjQgZDrZFFvJxFPvYgNAUzQb2RJENdTsMai3WOhWxSJPWaWG/0l4qD3El0/eslslIfuvGMyrwTBaE97M05q/wRmjfBL/uW0pKmjAzNXX3NkGlVezq5HoNr+k9f4FEhz6CEdSnRsOndLkMr9AwOZFFzp1E/jCHdrQ80AZKdMPpCZLR8sSU/NZa28jL9odkKKGggJ6IFKTEBAIRbUwuHZX1jS+j9ugv+U6sD+QtzGEiNl0yoqoAeC52kfLJRsWS9JpA1HEJ0m+J2Bk4v7M37TtOXtPXJmsqEPuBNhLLuK48zN1nz9HKIqlMCc6J6Lr4v479l9c/73dtXNZNCgV609uzwWqTqa6/p1TiuH4NJszJyp' # 🌟 토큰 정보 암호문
gateway:
ip: 127.0.0.1
🌟 참고
이렇게 두 토큰 정보를 양쪽에 똑같이 맞추는 방법도 있지만, 하나로 모을 수도 있다.
하나로 모은다는 것은,
ecommerce.yml
이든user-service.yml
이든 상위 개념으로application.yml
을 가지기 때문에,
ecommerce.yml
과user-service.yml
모두token.secret
정보를 지우고,application.yml
에 정의하게 되면,
두 서비스 모두application.yml
에 있는token.secret
정보를 공통으로 가지고 온다.
💛 개인 공부 기록용 블로그입니다. 👻