웹어플리케이션보안 난수화 토큰인증 2017. 9. 중부대학교 정보보호학과 이병천 교수
난수화 토큰인증을 이용한 인증유지 평문통신으로 안전한 인증 클라이언트 서버 𝑝𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶 𝐼𝐷, 𝐾 서버인증서 서버개인키 초기인증(ID/Pass) 전자서명 간편로그인 공개토큰, 비밀토큰 발급 (인증서토큰으로 암호화 전송) 서버인증서 인증서토큰 개인키 공개토큰,비밀토큰 로컬스토리지에 저장 공개토큰, 비밀토큰 계산 𝑝𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶 𝐼𝐷, 𝐾 𝑠𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶(𝑝𝑡𝑜𝑘𝑒𝑛,𝐾) 평문통신으로 안전한 인증 공개토큰, 현재시간, 난수화인증정보 난수화 인증정보 검증 난수화 인증정보 계산 Auth = H(time, stoken) 𝑠𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶 𝑝𝑡𝑜𝑘𝑒𝑛,𝐾 Auth =? H(time, stoken) 인증유지 현재시간에 따라 난수화된 인증정보 이용 HTTPS 보안통신채널 불필요
난수화 토큰인증을 이용한 인증유지 서버가 서명하여 발급한 아이디 - 서버 이외에는 위조 불가 - 서버만 검증 가능 공개토큰 𝑝𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶 𝐼𝐷, 𝐾 비밀토큰 𝑠𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶(𝑝𝑡𝑜𝑘𝑒𝑛,𝐾) 서버가 서명하여 발급한 비밀번호 - 아이디로부터 계산 가능 - 비밀키 key를 가진 서버만 계산 가능 - 인증유지 및 비밀통신에 사용
차례 1. 로그인시 2개의 토큰 발급 2. 프로필 페이지 접속시 난수화 토큰인증 적용 3. 새로운 컴포넌트 생성 및 등록 4. 일회용 비밀키를 이용한 메시지 암호화 5. 일회용 비밀키를 이용한 메시지 인증 6. JWT 인증을 이용하는 블로그페이지 작성 7. 대쉬보드 페이지 작성 8. 정적페이지 작성 (홈, 기술소개) 9. 서비스 빌드
1. 로그인시 2개의 토큰 발급 MeanAuthApp 서비스로부터 단계별로 수정하여 개발 서버측 클라이언트측 원본은 압축하여 백업해놓을 것, 또는 별도의 폴더로 보관 서버측 로그인시 토큰을 2개 발급하도록 수정 공개토큰 ptoken, 비밀토큰 stoken 클라이언트측 로그인시 발급한 토큰을 localStorage에 저장하도록 수정
두개의 토큰 발급 서버는 로그인된 사용자에게 두개의 토큰을 발급 요구사항 공개토큰: 공개 가능, 개인정보 전달용 (기존의 JWT와 동일 역할) 비밀토큰: 공개 불가, 인증정보 계산용 요구사항 토큰은 서버만이 계산할 수 있어야 함 (서버의 비밀정보 K 이용) 서버는 공개토큰으로부터 비밀토큰을 언제든지 계산 가능함
서버측 routes/users.js 파일 수정 User.comparePassword(password, user.password, (err, isMatch) => { if(err) throw err; if(isMatch) { const ptoken = 'JWT '+jwt.sign({data: user}, config.secret, { expiresIn: 604800 // 유효기간 1주일, 발급시점: 현재시간 }); const stoken = 'JWT '+jwt.sign({data: ptoken}, config.secret, { noTimestamp: true }); // 발급시점, 유효기간 삭제. res.json({ // 로그인 성공시 공개토큰, 비밀토큰, 사용자정보 응답 success: true, ptoken: ptoken, stoken: stoken, user: { id: user._id, name: user.name, username: user.username, email: user.email, address: user.address } }) } else { return res.json({success: false, msg: 'Wrong password'}); router.post('/authenticate', User.comparePassword(password, 기존의 jwt토큰을 공개토큰으로 명칭변경 비밀토큰은 공개토큰에 대한 서버의 서명 비밀토큰은 발급시점, 유효기간 지정하지 않음. 서버에서 언제든지 재계산할 수 있도록… 클라이언트에게 ptoken, stoken, user를 리턴 기존의 username/password로 로그인 성공 이후의 시나리오 변경
클라이언트측 login.component.ts 수정 수신된 데이터 3개를 저장 필요 - ptoken, stoken, user Auth.service.ts에서 storeUserData 함수 수정 필요
클라이언트측 auth.service.ts 수정 - pToken: 공개토큰 - sToken: 비밀토큰 (authToken은 필요 없지만 다른 모듈에서 사용중이므로 에러방지 위해 임시로 남겨둠, 개발 완료 후 삭제) 두개의 토큰과 사용자정보를 localStorage에 저장 로그인정보 저장을 위한 글로벌 변수에 수신한 정보를 지정
클라이언트측 auth.service.ts 수정 토큰 관련 함수들을 수정 로그아웃시 토큰과 사용자 정보를 지움 로그인 여부 확인시 공개토큰의 유효기간 확인
로그인 테스트 로그인 성공하면 토큰이 저장됨
2. 프로필 페이지 접속시 난수화 토큰인증 적용 auth = H(currT, stoken) JWT 토큰인증에서는 고정된 토큰을 오랜기간 사용 사용자의 고정된 토큰이 첨부되면 인증해줌. 도청공격에 취약. 보안통신채널 필수 사용 난수화 토큰인증에서는 현재시간과 비밀토큰의 해쉬값을 일회용인증정보로 전송 auth = H(currT, stoken) 서버는 전송된 공개토큰으로부터 비밀토큰을 계산하고 일회용 인증정보를 검증 암호 패키지로 forge를 사용 해쉬함수로는 SHA256을 사용
프로필 페이지 접속시 난수화 토큰인증 적용 먼저 해쉬함수를 사용할 수 있도록 forge 패키지 사용 준비 필요 서버측 node-forge 패키지 설치 프로젝트 폴더로 이동 > cd meanauthapp > npm install --save node-forge 클라이언트측 node-forge 패키지 설치 Angular-src 폴더로 이동 > cd angular-src
클라이언트측 profile.component.ts 수정 필요한 변수 선언 authService에서 선언한 3가지 함수를 이용 일회용인증정보 계산 getProfile 함수 호출시 3개의 입력변수를 사용하도록 수정 - 공개토큰, 현재시간, 일회용인증정보 에러 발생시 로그아웃을 하고 /login으로 리다이렉트
클라이언트측 auth.service.ts 수정 node-forge 패키지를 사용하기 위해서 forge 객체를 선언함 import * as forge from ‘node-forge’; 현재시간 출력 공개토큰 읽어와서 리턴 비밀토큰 읽어와서 리턴 Node-forge 의 forge.md.sha256 객체를 이용해서 현재시간과 비밀토큰값을 이용한 해시값 계산하여 리턴
클라이언트측 auth.service.ts 수정 Profile 페이지 접근을 위한 getProfile 함수 수정 - 3개의 변수를 입력 받도록 수정 - 3개의 변수를 헤더에 붙여서 서버로 전송 http 헤더에 이와 같이 임의의 정보를 추가하여 전송할 수 있음 - http.get 메서드로 서버에 전송하고 결과를 받아옴
서버측 routes/users.js 수정 forge를 사용하기 위한 객체 선언 auth: 수신한 일회용인증정보 authorization 헤더로 전송되어온 공개토큰으로부터 비밀토큰 계산 auth2: 서버에서 계산한 일회용인증정보 현재시간+비밀토큰의 해시값 클라이언트-서버 시간 차이 검증 (도청, 재전송 공격 방지) 일회용인증정보값이 동일하면 요청을 허용하고 사용자정보 리턴
프로필 접속 테스트 서버콘솔에 테스트 출력 프로필 페이지 보기 완성 난수화 토큰인증 적용 - 올바른 auth 값을 보내야 접근 가능
프로필 페이지 출력 변경 Profile.component.html <div *ngIf="user"> <h2 class="page-header"> 사용자 프로필 <small>난수화 토큰인증 적용</small> </h2> <ul class="list-group"> <li class="list-group-item">Name: {{user.name}}</li> <li class="list-group-item">Username: {{user.username}}</li> <li class="list-group-item">Email: {{user.email}}</li> </ul> <div class="alert alert-info" role="alert"> <h4> 프로필 페이지는 난수화 토큰인증 기술이 적용된 페이지입니다. <br> 브라우저는 현재 시간정보를 이용하여 접속시마다 난수화된 인증정보를 전송하고 서버는 이것의 유효성을 검증합니다. 그러므로 공격자가 통신을 도청하더라도 재사용할 수 없습니다. </h4> </div> <hr> <h2 class="page-header"> 이 페이지를 접근하기 위해 서버로 전송한 정보 </h2> <div class="alert alert-success" role="alert"> <h4> 공개토큰(Public Token) </h4> <textarea class="form-control" rows="8" cols="80"> {{ptoken}}</textarea> <div class="alert alert-warning" role="alert"> <h4> 현재시간(Current Time) </h4> <p>{{currT}}</p> <br> <h4> 난수화 인증정보(One-time Auth)</h4> <p>{{auth}}</p> <p>이 페이지를 여러번 접속해보면 서버로 전송되는 난수화 인증정보가 매번 바뀌는 것을 볼 수 있습니다. </p> Profile.component.ts에 있는 변수를 이렇게 직접 출력 가능 - 데이터바인딩
프로필 페이지 출력 변경
3. 새로운 컴포넌트 생성 및 등록 추가 사용할 4개의 컴포넌트 생성 Encrypt – 일회용 비밀키를 이용한 메시지 암호화 Mac - 일회용 비밀키를 이용한 메시지 인증 Blog – 표준 jwt 인증을 이용하는 블로그 페이지 About – 기술소개 (static page) Component 폴더로 이동 > cd angular-src/src/app/components > ng g component encrypt > ng g component mac > ng g component blog > ng g component about
App.module.ts 에 모듈 등록 확인 컴포넌트 등록 확인
App.module.ts 에 루트 등록 루트 등록 About: 로그인 안된 상태에서도 표시 Blog, encrypt, mac: 로그인된 상태에서만 표시 (AuthGuard 적용)
메뉴에 등록 navbar.component.html 수정 왼쪽 정렬 메뉴 오른쪽 정렬 메뉴
메뉴에 등록 메뉴 동작 테스트 (잘 동작함)
4. 일회용 비밀키를 이용한 메시지 암호화 클라이언트와 서버 사이에 보안통신을 위해서는 비밀키 공유/전송이 필요 클라이언트와 서버 사이에 보안통신을 위해서는 비밀키 공유/전송이 필요 https 기술을 적용하는 것이 일반적임 https: SSL/TLS 기술을 http에 적용한 웹통신보안 기술 Https가 적용되면 서버는 접속된 클라이언트의 세션정보를 유지 해야 함. 무상태 서비스 아님. 난수화 토큰인증을 이용한 비밀통신채널 형성 가능 난수화토큰인증 정보를 일회용 비밀키로 사용하여 메시지 암호 화/복호화 가능 세션정보 유지가 필요없는 무상태 암호화 서비스 auth = H(currT, stoken) key auth C = E(m,key)
난수화 토큰인증을 이용한 암호화 통신 클라이언트 서버 𝑝𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶 𝐼𝐷, 𝐾 𝑠𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶(𝑝𝑡𝑜𝑘𝑒𝑛,𝐾) 서버인증서 서버개인키 초기인증(ID/Pass) 전자서명 간편로그인 공개토큰, 비밀토큰 발급 (인증서토큰으로 암호화 전송) 서버인증서 인증서토큰 개인키 공개토큰,비밀토큰 로컬스토리지에 저장 공개토큰, 비밀토큰 계산 𝑝𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶 𝐼𝐷, 𝐾 𝑠𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶(𝑝𝑡𝑜𝑘𝑒𝑛,𝐾) 공개토큰, 현재시간, 암호문 세션키 계산 세션키 계산 key = H(time, stoken) 𝑠𝑡𝑜𝑘𝑒𝑛=𝐻𝑀𝐴𝐶 𝑝𝑡𝑜𝑘𝑒𝑛,𝐾 key= H(time, stoken) 테스트: 평문 복구하여 응답 세션키로 메시지 암호화 세션키로 메시지 복호화
UI 작성 Encrypt.component.html 수정 메시지전송 버튼을 눌렀을때 onEncryptSubmit 함수 실행
UI 작성 Encrypt.component.html 수정
UI 테스트
Encrypt.component.ts 수정 필요한 컴포넌트, 서비스를 등록하여 객체 생성 암호화 계산을 위해 forge 객체 추가 필요한 변수 선언 - plaintext : 평문 - currT : 현재시간 - key : 대칭키 - encrypted : 암호문 - decrypted : 복호화한 평문 필요한 서비스 등록
Encrypt.component.ts 수정 onEncryptSubmit 함수 선언 - 메시지전송 버튼을 눌렀을때 실행하는 함수 Auth.service.ts에서 getKey, getEncrypt, encryptedMessage 함수를 호출 - 이것을 생성해야 함
Auth.service.ts 수정 세계 언어 처리를 위한 UTF-8 인코딩 Key는 bytes array로 준비 hex로 변환하여 리턴 getKey 함수 현재시간, 비밀토큰으로부터 일회용인증값 auth를 생성하고 이것을 암호화키로 리턴하는 함수 getEncrypt 함수 평문(plaintext)을 비밀키(key)로 AES 암호화하여 암호문을 리턴하는 함수
Auth.service.ts 수정 encryptedMessage 함수의 역할 브라우저에서 생성한 암호문을 서버에 보내고 서버에서 복호화하여 보내주는 평문을 받아옴 암호문, 현재시간, 공개토큰을 파라메터로 보냄 헤더에 공개토큰, 현재시간, 암호문을 첨부하여 보냄 http.get 메서드로 보냄 서버에서 이런 요청을 받아 처리하는 서비스 users/encrypt 가 있어야 함
서버측 routes/users.js 수정 router.get('/encrypt', ) 패스 추가 헤더에서 공개토큰, 현재시간, 암호문을 획득 공개토큰으로부터 비밀토큰을 계산 현재시간, 비밀토큰으로부터 일회용인증정보(auth)를 계산 auth를 비밀키로 만듬 암호문을 복호화하여 평문(decrypted) 복구 평문을 클라이언트로 리턴
암호문 전송 테스트 같은 메시지를 여러 번 전송하면 시간에 따라 암호문이 계속 바뀜 - 일회용 비밀키를 사용하기 때문임
5. 일회용 비밀키를 이용한 메시지 인증 메시지 인증이란? 난수화 토큰인증을 이용한 메시지 인증 송수신자가 공유한 비밀키를 사용하여 송신자가 전송한 메시지 가 유효한지 여부를 수신자가 검증하는 서비스 Mac 값을 계산하여 메시지와 함께 전송 mac = HMAC(message, key) 난수화 토큰인증을 이용한 메시지 인증 난수화 토큰인증에서 계산되는 일회용 인증정보를 비밀키로 사 용하여 메시지 인증에 적용 auth = H(currT, stoken) key auth mac = HMAC(message, key)
UI 작성 Mac.component.html 수정 메시지전송 버튼을 눌렀을때 onMacSubmit 함수 실행
UI 작성 Mac.component.html 수정
UI 완성
Mac.component.ts 수정 필요한 컴포넌트, 서비스 등록 객체 생성 암호 계산을 위해 forge 객체 추가 필요한 변수 선언 - plaintext: 입력메시지 - currT: 현재시간 - key: 일회용 공유키 - mac: 메시지인증값 - msg : 서버의 응답 필요한 서비스 등록
Mac.component.ts 수정 onMacSubmit 함수 선언 - 메시지전송 버튼을 눌렀을때 실행하는 함수 세계언어 처리를 위한 UTF-8 인코딩 적용 onMacSubmit 함수 선언 - 메시지전송 버튼을 눌렀을때 실행하는 함수 Auth.service.ts에서 computeAuth, getMac, macMessage 함수를 호출 - 이것을 생성해야 함
Auth.service.ts 수정 computeAuth 함수 - 현재시간, 비밀토큰으로부터 일회용인증값 계산을 계산하여 리턴 - 현재시간, 비밀토큰으로부터 일회용인증값 계산을 계산하여 리턴 getMac 함수 - 입력메시지, 비밀키로부터 메시지인증값 계산하여 리턴 - SHA256 해쉬값 계산 macMessage 함수 - 입력메시지, 현재시간, 공개토큰, mac을 입력으로 받아서 - 이들 정보를 헤더에 넣은 패킷을 http.get 메서드로 서버로 전송하고 - 서버의 응답을 받아옴 - 서버에 get(‘users/mac’, )요청을 처리할 수 있는 서비스를 제공해야 함
서버측 routes/users.js 수정 router.get('/mac', ) 패스 추가 헤더에서 필요한 정보 수집 공개토큰으로부터 비밀토큰 계산 현재시간, 비밀토큰으로부터 일회용비밀키 key 계산 서버에서 계산하는 인증정보 mac2 계산 mac1, mac2가 같으면 메시지인증이 유효함
메시지 인증 테스트 같은 메시지를 여러 번 전송하면 시간에 따라 MAC이 계속 바뀜 - 일회용 비밀키를 사용하기 때문임
6. JWT 인증을 이용하는 블로그페이지 작성 기존의 JWT 인증을 이용했던 profile 서비스를 blog 라 는 이름의 서비스로 제공 공개토큰 ptoken을 헤더에 첨부하여 반복 전송하는 방식 서버는 ptoken의 유효기간을 확인하여 유효기간동안 인증 유지 Passport를 이용한 인증
UI 작성 Blog.component.html 작성
Blog.component.ts 작성 필요한 서비스 등록, 객체 생성 사용자 정보 user와 토큰 ptoken 사용 Auth.service.ts에 getBlog 함수 생성 필요
Auth.service.ts 수정 공개토큰 ptoken을 헤더에 첨부하여 http.get 메서드로 보내고 서버의 응답을 받아옴 서버에 get(‘/blog’, ) 메서드 작성 필요
서버측 routes/users.js 수정 기본 JWT 인증 적용 인증시 사용자 정보 전송
Blog 서비스 테스트
7. 대쉬보드 페이지 작성 대쉬보드 사용자의 개인정보/서비스 관리에 사용되는 페이지 ID/pass로 로그인하면 처음 리다이렉트되는 페이지 로그인된 상태에서는 접근 가능한 페이지 JWT나 난수화토큰인증이 적용되지 않음 토큰정보를 보여주도록 수정함
UI 작성 Dashboard.component.html 작성
Dashboard.component.ts 작성 이미 발급되어 있는 토큰을 읽어와서 화면에 표시해주는 간단한 기능만 수행함 토큰 변수 선언 토큰을 localStorage에서 읽어옴 이것이 화면에 표시됨
대쉬보드 페이지 테스트
8. 정적 페이지 작성 (홈, 기술소개) 정적 페이지는 html 파일만 작성하면 됨. 그림, 문서등 단순한 링크 파일은 angular-src/src/assets 폴더에 저장하여 사용 (angular-src/src 의 하부 폴더에 있으면 됨) angular-src/src/assets/rtoken.jpg
Home.component.html 작성 그림파일 링크 - assets 폴더에 저장하여 사용
Home 페이지 테스트 그림파일 링크
기술소개 페이지 작성 About.component.html 작성 그림파일 링크 Pdf 문서 링크
기술소개 페이지 테스트 그림파일 링크 Pdf 문서 링크
9. 서비스 빌드 서비스 빌드 Express 웹서비스로 전체 서비스 접속 가능 공용서버에서 서비스 > cd angular-src > ng build 개발된 angular-src 폴더의 내용이 컴파일되어 public 폴더로 이 전됨 Express 웹서비스로 전체 서비스 접속 가능 공용서버에서 서비스 Auth.service.ts에서 지정된 prepEndpoint(ep)의 리턴주소를 수정 폴더 전체를 서버에 업로드 서버 실행 외부 클라우드 서버에서 서비스