Vue 암호화, Spring(java)복호화 (RSA, crypto, cipher, 네이버 로그인 소스코드)

kwak woojong·2023년 3월 28일
0

스케치북

목록 보기
1/1
post-thumbnail

네이버 로그인 할 때 post 내용을 본 적 있음?

fetch로 보면 안보이고
문서탭으로 봐야 보임

분명 post를 하고 있다.

우리가 만약 로그인 로직을 만들었다면 HTTPS를 믿고 걍 id, pw를 보냈을 것

근데 이 친구들은 비어있다. 맨 마지막id, pw보임?
위에서 3번째인 encpw에 암호화 돼서 나가는 거로 보인다.

소스를 보면 이렇게 생김. 물론 더 많은 소스코드가 있는데, 이것만 보자

id랑 pw를 받음.

rsa 키를 생성하고 암호화를 해준다. 그리고 id랑 pw를 비워주고 끝남

대충 봐도 id랑 비번 합친 후에 encpw value에 넣어주는 듯 함.

즉 네이버도 HTTPS를 쓰지만 아이디 비번을 암호화 해서 넘기고 있다.

나도 뭐 저 정도는 Vue 초짜지만 하겠지 싶어서 시도함.


AES냐 RSA냐

AES는 똑같은 키를 가지고 암호화 복호화를 한다.

우리가 앞단에 아무리 키를 숨기려 해도 F12누르면 다 튀어나옴.

키가 공개되면 절대로 안된다. 바로 파싱이 쌉가능하기 때문임

그러므로 일단 AES는 탈락.

RSA는 Public Key (이하 공개키)로 암호화하고 Private Key(이하 개인키) 로 복호화할 수 있다.

즉 비대칭키를 가지고 있기 때문에 공개키로 암호화만 신나게 해봐야 복호화가 안 됨.

그래서 네이버도 저 RSA를 쓰고 있는 것으로 사료된다.


일단 키를 구해볼까

https://travistidwell.com/jsencrypt/demo/

이 사이트에서 RSA 키를 구할 수 있다. 물론 걍 JAVA에서 만들어서 넘겨도 될 것만 같음. 아직 안해봄...

지금은 어차피 학습용이니까 일단 저 사이트에서 키를 만들어서 넘겨보자.

pkcs8과 pkcs1

상기 키값을 보면 ----BEGIN RSA PRIVATE KEY ----- 로 시작하는 것을 볼 수 있음.
이 친구는 PKCS1 형식을 가지고 있음.
PKCS8은 -----BEGIN PRIVATE KEY ----- 로 시작한다. 이거 개쩌는 복선임.


Vue에서 일단 공개키로 암호화


<template>
  <div id="Login">
    <form v-on:submit.prevent="encryptLoginData(), login()" class="login-box">
      <h1> 로그인 </h1>
      <input id="encryptIdPassword" v-model="form.encryptIdPassword" type="hidden">
      <p> 아이디 </p>
      <input id="userId" v-model="form.userId" type="text">
      <p> 비밀번호 </p>
      <input id="password" type="password" v-model="form.password">
      <input type="submit" value="로그인">
    </form>
  </div>
</template>

간단하게 뷰에서 테스트 하기 위한 템플릿을 만들어 줬다.
submit을 하면 encryptLoginData 함수가 발동하고 login이 발동함

encryptLoginData는 이름에서 보다싶이 아이디 비번을 암호화 하는 함수다.

encryptLoginData() {
      const id = this.form.userId;
      const pw = this.form.password;
      const publicKey = process.env.VUE_APP_PUBLIC_KEY.replaceAll('|', '\n');

      const buffer = Buffer.from(`${id}|${pw}`);
      const encrypt = crypto.publicEncrypt({
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_PADDING,
      }, buffer);
  
      this.form.encryptIdPassword = encrypt.toString('base64');
      this.form.userId = '';
      this.form.password = '';
    },

난 node를 아주 간략하게 다뤄봄. 그래서 코드가 좀 더럽다.

id, pw값을 가져온 후 publicKey를 env에서 뜯어온다. 그리고 내가 임의로 준 |를 \n으로 바꿈

그리고 id랑 비번을 합치는데 |로 구분을 지어줬다.
그리고 crypto를 사용해 공개키 암호화를 시도함.

crypto는 Node 기본 모듈이므로 모두가 사용 가능하다. 예전엔 따로 의존성 넣어줬어야 했다는 듯!

하여튼 암호화 하고 난 값인 encrypt를 .toString을 사용해서 base64 형식으로 만들어준 다음
폼 데이터에 넣어줬다. 그리고 네이버처럼 id랑 비번을 비워줌.
toString엔 base64를 안써도 상관 없다. 다른 인코딩 방식을 써서 잘 넘기고 디코딩만 할 수 있다면 상관 없을 듯. utf-8로 보내도 되지 않을까?
하여튼 암호화된거 깨질까봐 base64로 보내봄. Java에 Base64Decoder도 있으니까 ㅎㅎ

뽀오스트를 한 번 해봤다. 암호화 하기 이전에는 페이로드에 id랑 pw가 그대로 노출 됐었음.


옆에서 보면 비번 다 보이죠?



이젠 아니야


Spring에선 잘 되냐?

이 부분에서 힘들었음.

   const encrypt = crypto.publicEncrypt({
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_PADDING,
      }, buffer);

위에 Vue에서 암호화하는 친구를 다시 가져와 봤음.
Padding이 RSC_PKCS1_PADDING임을 알 수 있다.
당연한게 RSA 키가 PKCS1 형식을 따르고 있기 때문임.

그럼 JAVA에도 복호화 하려면 KEY 복호화 클래스가 PKCS1을 지원 해야 한다.


응 안해

JAVA에선 PKCS8만 지원한다. PKCS1은 지원 안함.

후... 그럼 Javascript의 crypto에서 PKCS8을 지원하면 되잖아?


응 PKCS8 안해

이런 야발 이 부분에서 걸릴 줄은 상상도 못함.
둘 중 하나는 Key 형식을 바꿔서 저장하고 있어야함.

PKCS8 -> PKCS1 으로 바꿔서 가지고 있거나
PKCS1 -> PKCS8 로 바꾸거나임.

일단 Gen 사이트 형식은 PKCS1이므로 PKCS1 -> PKCS8을 시도하기로 함.
아마 나만 이런 문제를 가지고 있진 않았을 거임.

https://8gwifi.org/pemconvert.jsp
이제보니까 jsp로 만든 사이트네 ㅋㅋㅋ 옛날 곳인가
하여튼 여기선 PKCS1 형식을 PKCS8로 바꿀 수 있다.
바꿔서 해보니까 잘 됨. 다른 방법으론 openssl을 사용하는 방법도 있음. 검색 ㄱ


짜짠 키를 아주 잘 바꿔줬음. 일단 복호화가 되기도 하니까 ㅋㅋㅋㅋ 잘 된거겠지.

@Slf4j
@Component
public class DecryptService {

    private Cipher cipher = Cipher.getInstance("RSA");
    
    // 생성자 생략
    
    public String decryptLoginData(String encryptIdAndPassword) throws InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] decryptForBase64 = Base64.getDecoder().decode(encryptIdAndPassword);
        String stringKey = "대충 RSA 키";

        stringKey = stringKey.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replaceAll("\\n", "")
                    .replace("-----END PRIVATE KEY-----", "");

        byte[] decode = Base64.getDecoder().decode(stringKey);

        PrivateKey privateKey = KeyFactory.getInstance("RSA")
                .generatePrivate(new PKCS8EncodedKeySpec(decode));

        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decrypted = cipher.doFinal(decryptForBase64);

        return new String(decrypted, "UTF-8");
    }

    public void splitLoginData(MemberLoginDto memberLoginDto, String userIdAndPassword) {
        String[] split = userIdAndPassword.split("\\|");
        memberLoginDto.setUserId(split[0]);
        memberLoginDto.setPassword(split[1]);
    }
    

아으 왜케 길어 일단 키가 긴거 같아서 줄임.

그리고 이제 보니까 막 하려고 하다보니 throws가 개많음. 다 catch를 하든가 해야겠음.

하여튼 cipher를 만들어주고 RSA를 미리 선언하셈. 저 서비스는 컴포넌트로 스프링 컨테이너에 속해 있음. JWT인증 필터에서 고맙게 잘 쓰일 예정이다.

우선 뷰에서 보내준 encryptIdAndPassword를 필터에서 어떻게 잘 뜯은 다음 여기로 넘겨준다.

그리고 Base64로 된 그 친구를 디코딩함. 그럼 바이트시퀀스가 나온다. 이걸 나중에 복호화 할거임.

그리고 PKCS8 형식의 암호키에서 씰대 없는 부분을 뜯어준다. vue에선 알아서 잘 뜯는거 같더라니 여기선 수동으로 뜯어야함. 하튼 이후 키를 base64로 또 디코드 해줌.

이후 privatekey를 생성하는데 키 팩토리 클래스를 이용해서 PKCS8EncodedKeySpec을 사용해준다. 그럼 privateKey가 생성됨. 여기서 RSAKey로 캐스팅 해도 될 것 같긴 한데 일단 저렇게 그대로 사용해줬다. 그리고 cipher을 사용해서 복호화 해준 후 복호화 한 바이트배열을 String으로 바꿔준다.

여기서 toString()을 안쓰고 new String(value, encoding)을 쓴 이유는 안 저러면 제대로 안뜯김. 이유는 좀 복잡할 거 같은데 Base64를 기반으로 Byte시퀀스를 디코딩 해놨고 그걸 복호화 했으니 Base64 형식으로 뜯긴 것 아닐까 싶음. 물론 이건 정확한 건 아님. 누가 알면 댓글 좀 달아주세용. 하튼 암호화 보안쪽은 넘나 머리 아픈 것이야


하튼 응답해더에 2개의 토큰이 잘 붙어온다. 작동 잘해씀!


잡설

일단 프로퍼티 파일에 개인키를 암호화해서 잘 넣어두면 아마 보안쪽으론 괜찮아 보임.

실은 Java에서 RSA 공개키, 개인키를 생성해두고 Vue에서 get으로 공개키만 뜯어 온 뒤 그걸로 암호화 하는 것은 어떨까 싶었음.

Java에서 나도 모르게 생긴 키가 있을 거고 그걸 굳이 application.yml 환경변수 파일에 안넣어도 되니까...

근데 PKCS8 형식만 튀어나올테니 Vue에서 바로 쓰긴 무리가 있어 보인다.
PEM이든 환경변수든 잘... 숨기야 하겠다.


아 개인 키를 저렇게 공개적으로 써두고 깃헙에 올리니까 저런 메일이 옴 ㅋㅋㅋ

그들이 우릴 지켜주고 있어용

profile
https://crazyleader.notion.site/Crazykwak-36c7ffc9d32e4e83b325da26ed8d1728?pvs=4<-- 포트폴리오

0개의 댓글