js로 AWS를 생성한 MFA의 "totp.js"읽기

개시하다


외부 프로그램 라이브러리를 사용하지 않는 자바스크립트의 TOTP로 totp를 설치합니다.js를 만들었지만 너무 힘을 줘서 해설 기사를 쓰지 않으면 자신도 모르기 때문에 이곳에서 공양한다.
이 기사는 이른바'조직의 기술 전달 방법을 강화하다'이 말한'뒷면 설계도'다.(참고로 이 책은 엔지니어, 프로젝트 매니저, 제품 관리자, 폐품 관리자들에게 꼭 읽어야 할 책이다. 도서관에도 책이 많아서 한 시간 정도면 다 읽을 수 있을 것 같다.)
https://gist.github.com/matobaa/fd519dbcfff2c30cb56597194d1a4541
totp.js
//javascript:
(secret=>{
var b32=s=>[0,8,16,24,32,40,48,56]
 .map(i=>[0,1,2,3,4,5,6,7]
  .map(j=>s.charCodeAt(i+j)).map(c=>c<65?c-24:c-65))
  .map(a=>[(a[0]<<3)+(a[1]>>2),
   (a[1]<<6)+(a[2]<<1)+(a[3]>>4),
   (a[3]<<4)+(a[4]>>1),
   (a[4]<<7)+(a[5]<<2)+(a[6]>>3),
   (a[6]<<5)+(a[7]>>0),
  ]).flat(),
 trunc=dv=>dv.getUint32(dv.getInt8(19)&0x0f)&0x7fffffff,
 c=Math.floor(Date.now()/1000/30);
 crypto.subtle.importKey('raw',new Int8Array(b32(secret)),{name:'HMAC',hash:{name:'SHA-1'}},true,['sign'])
 .then(k=>crypto.subtle.sign('HMAC',k,new Int8Array([0,0,0,0,c>>24,c>>16,c>>8,c])))
 .then(h=>document.querySelector('#mfacode').value=('0'+trunc(new DataView(h))).slice(-6))
})('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
다중 요소 인증에 사용되는 QR 코드(예: AWS)에 다음 정보가 포함되어 있습니다.
otpauth://TYPE/LABEL?secret=XXX&Issuer=YYY
이 시크릿을 추출하고 위에서 지정한 마지막 호출을 통해 TOTP의 6비트를 #mfacode에 입력합니다.책갈피를 설정하면'스마트폰을 시작하는 Google Authenticator, Issuer를 선택하고 눈으로 6자리 입력을 본다'는 번거로움이 없어진다.

실장 해설


그런 다음 설치에 대해 설명합니다.

책갈피 설명


#L1
//javascript:
책갈피 접두어.자바스크립트의 설치 자체를 책갈피에 넣고 한 페이지를 표시하는 상태부터 이 책갈피를 여는 동작을 통해 자바스크립트의 '책갈피' 를 실행할 수 있습니다 javascript:.
이 줄 자체는 이미 주석을 달았기 때문에 실현의 의미가 없지만 이것은 이 실현이 책갈피라는 것을 나타낸다.

지금 실행 공식


#L2,17
(secret=>{ /* ... */ })('XXXXX')
자바스크립트의 함수 정의문仮引数=>{関数定義} 뒤에 바로 (実引数)을 붙여서 실제 매개 변수를 정의된 함수에 전달하여 실행합니다.const func = (secret => {}); func('XXXXX') 내연 전개 형식.function (仮引数) {} 의 양식으로 설명된 경우 함수 정의 이전(을 추가하지 않으면 해석기는 함수 정의문으로 해석하지만 추가( 등을 통해 실시간 실행식으로 해석할 수 있다.+!부터 시작되는 의식도 있다.
Immediately Invoked Function Expression; 지금 실행 공식

base 32 디코더 설치


#L3,11
var b32=s=>[0,8,16,24,32,40,48,56]
 .map(i=>[0,1,2,3,4,5,6,7]
  .map(j=>s.charCodeAt(i+j)).map(c=>c<65?c-24:c-65))
  .map(a=>[(a[0]<<3)+(a[1]>>2),
   (a[1]<<6)+(a[2]<<1)+(a[3]>>4),
   (a[3]<<4)+(a[4]>>1),
   (a[4]<<7)+(a[5]<<2)+(a[6]>>3),
   (a[6]<<5)+(a[7]>>0),
  ]).flat(),
매개 변수로 수신된base 32 문자열을 정수의 배열로 되돌려줍니다.base32의 상세한 규격 참조rfc4648.간단하게 말하면 문자A~Z를 정수치 0~25, 문자2~7를 정수치 26~31로 고친 후 5자리로 표시된 문자를 연결하여 8자리에 따라 정수로 분할한다.8자마다 5바이트를 생성합니다.
base 32 변환 테이블
문자
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
값(10진수)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
값(16진수)
00
01
02
03
04
05
06
07
08
09
0A
0B
0C
0D
0E
0F
문자
Q
R
S
T
U
V
W
X
Y
Z
2
3
4
5
6
7
값(10진수)
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
값(16진수)
10
11
12
13
14
15
16
17
18
19
1A
1B
1C
1D
1E
1F
변환 예:
  • base 32 문자열NQC42JFR ULD4RJP7
  • 변환표를 통해 수치화0D 10 02 1C 1A 09 05 11 | 14 0B 03 1C 11 09 0F 1F
  • 5자리로 표시01101 10000 00010 11100 11010 01001 00101 10001 | 10100 01011 00011 11100 10001 01001 01111 11111
  • 8자리마다 구분01101100 00000101 11001101 00100100 10110001 | 10100010 11000111 11001000 10100101 11111111
  • 16진수로 표시6C 05 CD 24 B1 A2 C7 C8 A5 FF
  • 실제 장비로 돌아가기:
    #L3,5
    s=>
    [0,8,16,24,32,40,48,56].map(i=>[0,1,2,3,4,5,6,7].map(j=>s.charCodeAt(i+j))
    .map(c=>c<65?c-24:c-65)
    )
    
    8글자마다 "0글자에서 7글자를 꺼내 변환표로 변환합니다."
    생성 구조[ [0D, 10, 02, 1C, 1A, 09, 05, 11], [14, 0B, 03, 1C, 11, 09, 0F, 1F], ...]의 결과.
    #L6,9
    a=>[(a[0]<<3)+(a[1]>>2),
       (a[1]<<6)+(a[2]<<1)+(a[3]>>4),
       (a[3]<<4)+(a[4]>>1),
       (a[4]<<7)+(a[5]<<2)+(a[6]>>3),
       (a[6]<<5)+(a[7]>>0),]
    
    수신 길이 8의 진열[0D, 10, 02, 1C, 1A, 09, 05, 11]을 8비트 단위로 다시 구분[6C, 405, 1CD, D24, B1]하여 길이가 5의 진열을 형성한다.
  • 제1요소 "0D를 왼쪽으로 3자리 옮긴 후의 값과 10을 오른쪽으로 2자리 옮긴 후의 값의 합"
  • 두 번째 요소는'10을 왼쪽으로 6자리 옮긴 후의 값, 02를 왼쪽으로 1자리 옮긴 후의 값, 1C를 오른쪽으로 4자리 옮긴 후의 값의 합'
  • 이다.
  • 제3요소'1C를 왼쪽으로 4자리 옮긴 후의 값과 1A를 오른쪽으로 1자리 옮긴 후의 값의 합'
  • 네 번째 요소'1A를 왼쪽으로 7자리 옮긴 값, 09를 왼쪽으로 2자리 옮긴 값, 05를 오른쪽으로 5자리 옮긴 값의 합'
  • 다섯 번째 요소'05를 왼쪽으로 5자리 옮긴 후의 값과 11을 오른쪽으로 0자리 옮긴 후의 값의 합'
  • #L11
    .flat()
    
    [ [6C, 405, 1CD, D24, B1], [A2, 2C7, 1C8, 8A5, 1FF], ...]의 배열[ 6C, 405, 1CD, D24, B1, A2, 2C7, 1C8, 8A5, 1FF, ...]의 평평한 모양으로 변했다.flat()는 ECMAScript 2019에 추가된 기능입니다.

    (잠시 휴식)

  • Q: 필요한 것은 아래 8비트가 아니라?상위를 뛰어넘는 비트는 어떡하지?
  • A:네.거추장스러웠지만 이후 Int8 Aray에서 받아들였을 때 상위 비트가 무시당해 개의치 않았다.
  • Q: Int8Array 높은 자리를 깎는다는 말은 안 썼지만 한번 해보고 싶은데?
  • A:응...ToInt8전환할 것 같아.4항에서는'Let int 8비트 be int modulo2^8'처럼 상위 비트를 줄였다.
  • Q:b32는 호출자의 Int8 Aray화 규칙을 제정하는 것이 아니라 b32에서 Int8 Arry화를 해야 하는가?
  • A:그러고 보니 지적하신 바와 같습니다!디자인 개선 요점!
  • Q: var이 아니라 const로 받아야 하나요?
  • A: 문자 수
  • Q:[0,1,2,3,4,5,6,7].map()이런 건 믿음직하지 않다?
  • A:글자수가 적으면 좋겠어요.substring 후 한 글자 한 글자 보내면 되죠
  • Q:내 생각엔비트 논리 및+아닌?
  • A: 보리이삭~ (회복 필요없음)
  • Q: 마젠다
  • A: 아니요
  • TOTP 설치


    TOTP(Time-Based One-Time Password)로서 RFC 6238과 RFC 4226이 정의한 것을 설치합니다.
    #L12,18
     trunc=dv=>dv.getUint32(dv.getInt8(19)&0x0f)&0x7fffffff,
     c=Math.floor(Date.now()/1000/30);
     crypto.subtle.importKey('raw',new Int8Array(b32(secret)),{name:'HMAC',hash:{name:'SHA-1'}},true,['sign'])
     .then(k=>crypto.subtle.sign('HMAC',k,new Int8Array([0,0,0,0,c>>24,c>>16,c>>8,c])))
     .then(h=>document.querySelector('#mfacode').value=('0'+trunc(new DataView(h))).slice(-6))
    
    rfc6238
    TOTP = HOTP(K, T)
    T = (Current Unix time - T0)/X
    T0의 값은 0입니다.X의 값은 30입니다.
    rfc4226
    HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
    K는 공유 키
    C는 카운터(변경된 값)
    Step 1: HS = HMAC-SHA-1(K,C) ... HS가 20바이트인 해시 값
    Step2-1: HS의 마지막 4자리 꺼내기 → 오프셋 0~15
    Step2-2: HS의 오프셋 바이트에서 4바이트 꺼내기
    Step2-3: 하위 31비트 제거
    3단계: 10진수 및 6자 분리
    설치 순서를 살펴보겠습니다.
    #L12
    var trunc=dv=>dv.getUint32(dv.getInt8(19)&0x0f)&0x7fffffff;
    
  • | ... 2-1단계: HS 마지막 4비트 체크 아웃
  • _offset_ = dv.getInt8(19)&0x0f ... Step2-2: HS의 오프셋 바이트에서 4바이트(32비트) 추출
  • dv.getUint32( _offset_ ) ... 2-3단계: 하위 31비트 꺼내기
  • #L13
    var c=Math.floor(Date.now()/1000/30);
    
    TOTP에서 사용되는 카운터가 계산됩니다.&0x7fffffff의 밀리초를 초로 환산하여 30을 나눈다.
    #L14,15
    crypto.subtle.importKey('raw',new Int8Array(b32(secret)),
            {name:'HMAC',hash:{name:'SHA-1'}},true,['sign'])
    .then(k=>crypto.subtle.sign('HMAC',k,new Int8Array([0,0,0,0,c>>24,c>>16,c>>8,c])))
    
    HMAC-SHA-1은 내장형 라이브러리Web Crypto API를 사용하여 계산할 수 있습니다.우선 HMAC-SHA-1용 크립토키SubtleCrypto.importKey()를 사용하고SubtleCrypto.sign() 이 크립토키를 먹게 함으로써 계산 결과를 얻는다.
    두 함수 모두 Promise로 되돌아오기 때문에 결과를 수신하려면 Date.now()를 사용해야 한다.
    #L16
    .then(h=>document.querySelector('#mfacode').value=('0'+trunc(new DataView(h))).slice(-6))
    
    then에서 계산 결과를 받아서 6개의 문자를 추출하여 mfacode라는 ID를 가진 DOM 요소에 저장합니다.간혹 자릿수가 부족한 경우가 있기 때문에 머리에 문자.then()를 붙여 보충한다.

    수확하다.

  • Q:Promise 결과를 await로 쓰면 더 잘 쓰겠죠?
  • A:await 뒤에 공백을 두어야 하지만 책갈피에 공백을 사용하지 않으려고 (% 20을 하나하나 써야 함) 하기 때문에then으로 바꿨습니다.
  • Q: 그렇다면 new Int8 Aray ()는 Int8 Aray입니다.프롬()을 원한다면?
  • A:하하.그렇게 지도 모른다, 아마, 아마...
  • Q:0은 한 글자만 보충했는데 괜찮아요?부족한 시도?
  • A:하하.그렇게 지도 모른다, 아마, 아마...
  • 끝말


    여기까지 읽어줘서 고마워요.
    질문이나 지적이 있으면 가볍게 댓글로 남겨주세요.여러분의 소감을 환영합니다.

    좋은 웹페이지 즐겨찾기