車輪——JWT加密與解密制作自己的token



#
! /usr/bin/python3 # -*- coding :utf-8 -*- import base64
# 必須是b格式 加密 a
=base64.b64encode(b'xuyueyou') print(a)


#解密

b= base64.b64decode(a )
print(b)
b'eHV5dWV5b3U='
b'xuyueyou'

1、什么是“可打印字符”呢?為什么要用它來傳輸 8Bit字節碼呢?

        Base64一般用於在 HTTP協議下傳輸二進制數據,由於 HTTP協議是文本協議,所以在 HTTP協議下傳輸二進制數據需要將二進制數據轉換為字符數據。然而直接轉換是不行的。因為網絡傳輸只能傳輸可打印字符。

        什么是可打印字符?在ASCII碼中規定,0~31、127這33個字符屬於控制字符,32~126這95個字符屬於可打印字符,也就是說網絡傳輸只能傳輸這95個字符,不在這個范圍內的字符無法傳輸。那么該怎么才能傳輸其他字符呢?其中一種方式就是使用Base64。

2、Base64的編碼原理

       Base64的原理比較簡單,每當我們使用Base64時都會先定義一個類似這樣的數組:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
        Base64采用了 "A-Z、a-z、0-9、+、/" 64個可打印字符,這是標准的Base64協議規定。在日常使用中我們還會看到 “=”或“==” 號出現在Base64的編碼結果中,“=”在此是作為填充字符出現,后面會講到。

       Base64就是使用這64個可打印字符來表示二進制數據的方法。Base64的索引與對應字符的關系如下表所示:

 

 

3、具體轉換步驟

        第一步,將待轉換的字符串每三個字節分為一組,每個字節占 8bit,那么共有24個二進制位。

        第二步,將上面的24個二進制位每6個一組,共分為4組。

        第三步,在每組前面添加兩個0,每組由6個變為8個二進制位,總共32個二進制位,即四個字節。

        第四步,根據Base64的索引與對應字符的關系獲得對應的字符值。

從上面的步驟我們發現:

         Base64字符表中的字符原本用6個bit就可以表示,現在前面添加2個0,變為8個bit,會造成一定的浪費。因此,Base64編碼之后的文本,要比原文多大約三分之一。

         為什么使用3個字節一組呢?因為6和8的最小公倍數為24,三個字節正好24個二進制位,每6個bit位一組,恰好能夠分為4組。

4、示例說明

   1)以下圖的表格為示例,我們具體分析一下整個過程。

      

        第一步:字符串“Man”為3個字節,“M”、“a”、"n"對應的ASCII碼值分別為77,97,110,對應的二進制值是01001101、01100001、01101110。如圖第二三行所示,由此組成一個24位的二進制字符串。

        第二步:如圖紅色框,將24位每6位二進制位一組分成四組。

        第三步:在上面每一組前面補兩個0,擴展成32個二進制位,此時變為四個字節:00010011、00010110、00000101、00101110。分別對應的值(Base64編碼索引)為:19、22、5、46。

        第四步:用上面的值在Base64編碼表中進行查找,分別對應:T、W、F、u。因此“Man”Base64編碼之后就變為:TWFu。

   2)位數不足情況

       上面是按照三個字節來舉例說明的,如果字節數不足三個,那么該如何處理?

        

        兩個字節:兩個字節共16個二進制位,依舊按照規則進行分組。此時總共16個二進制位,每6個一組,則第三組缺少2位,用0補齊,得到三個Base64編碼,第四組完全沒有數據則用“=”補上。因此,上圖中“BC”轉換之后為“QKM=”;

        一個字節:一個字節共8個二進制位,依舊按照規則進行分組。此時共8個二進制位,每6個一組,則第二組缺少4位,用0補齊,得到兩個Base64編碼,而后面兩組沒有對應數據,都用“=”補上。因此,上圖中“A”轉換之后為“QQ==”;

    3)注意事項

        大多數編碼都是由字符串轉化成二進制的過程,而Base64的編碼則是從二進制轉換為字符串。與常規恰恰相反,

        Base64編碼主要用在傳輸、存儲、表示二進制領域,不能算得上加密,只是無法直接看到明文。也可以通過打亂Base64編碼來進行加密。

        中文有多種編碼(比如:utf-8、gb2312、gbk等),不同編碼對應Base64編碼結果都不一樣。

延伸:

        上面我們已經看到了Base64就是用6位(2的6次冪就是64)表示字符,因此成為Base64。同理,Base32就是用5位,Base16就是用4位。大家可以按照上面的步驟進行演化一下。

 

 

車輪——JWT加密與解密

JWT - json-web-token

1,三大組成

​ 1,header

​ 格式為字典-元數據格式如下

```python
{'alg':'HS256', 'typ':'JWT'}
#alg代表要使用的 算法
#typ表明該token的類別 - 此處必須為 大寫的 JWT
```

​    該部分數據需要轉成json串並用base64 加密

 

​    2,payload

​    格式為字典-此部分分為公有聲明和私有聲明

公共聲明:JWT提供了內置關鍵字用於描述常見的問題

此部分均為**可選項**,用戶根據自己需求 按需添加key,常見公共聲明如下:

exp: time.time() + 5*60

```python
{'exp':xxx, # Expiration Time 此token的過期時間的時間戳
'iss':xxx,# (Issuer) Claim 指明此token的簽發者
'aud':xxx, #(Audience) Claim 指明此token的簽發群體
'iat':xxx, # (Issued At) Claim 指明此創建時間的時間

'username''guoxiaonao',
'uid': '1'
}
```

​    私有聲明:用戶可根據自己業務需求,添加自定義的key,例如如下:

```python
{'username': 'guoxiaonao'}
```

​    公共聲明和私有聲明均在同一個字典中;轉成json串並用base64加密

​    3,signature 簽名

​    簽名規則如下:

​    根據header中的alg確定 具體算法,以下用 HS256為例

​    HS256(自定義的key , base64后的header + '.' + base64后的payload)

​    h = hmac.new(b'key', b'123456', digestmod='SHA256')

​ 解釋:用自定義的key, 對base64后的header + '.' + base64后的payload進行hmac計算

 

2,jwt結果格式

​    base64(header) + '.' + base64(payload) + '.' + base64(sign)

​    最終結果如下:

  ```python
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1b3hpYW9uYW8iLCJpc3MiOiJnZ2cifQ.Zzg1u55DCBqPRGf9z3-NAn4kbA-MJN83SxyLFfc5mmM'
```

3,校驗jwt規則

​    1,解析header, 確認alg #

  ​ 2,簽名校驗 - 根據傳過來的header和payload按 alg指明的算法進行簽名,將簽名結果和傳過來 的sign進行對比,若對比一致,則校驗通過

  ​ 3,獲取payload自定義內容

 

 

 

輪子:

#! /usr/bin/python3
# -*- coding :utf-8  -*-
#生成hmac對象
#第一個參數為加密的key,bytes類型,
#第二個參數為欲加密的串,bytes類型
#第三個參數為hmac的算法,指定為SHA256
#
import hmac
import json
import copy
import base64
import time

class JWT:
    def __init__(self):
        pass

    # 去等號
    @staticmethod
    def b64encode(j_s):
        return base64.urlsafe_b64encode(j_s).replace(b'=', b'')

    @staticmethod
    def b64decode(b_s):
        # 補全簽發時 替換掉的 等號
        rem = len(b_s) % 4
        if rem > 0:
            b_s += b'=' * (4 - rem)
        return base64.urlsafe_b64decode(b_s)



    @staticmethod
    def encode(payload, key, exp=3000):
        # 創建head
        header={'alg':'HS256','typ':'JWT'}
        # separators=(',',':')是防止有空格的出現,','用什么相連,':'key和value用什么相連
        # sort_keys = True json串按key排序輸出
        header_j=json.dumps(header, separators=(',',':'),sort_keys=True)
        # base64加密json字符串
        # header_bs=base64.urlsafe_b64encode(header_j.encode())
        header_bs = JWT.b64encode(header_j.encode())

        # 創建payload
        payload=copy.deepcopy(payload)
        # 創建過期時間標記
        payload['exp']= int(time.time()+exp)
        # json payload字符串
        payload_j=json.dumps(payload,separators=(',',':'),sort_keys=True)
        # base64加密json字符串
        # payload_bs=base64.urlsafe_b64encode(payload_j.encode())
        payload_bs = JWT.b64encode(payload_j.encode())

        # 生成簽名sign:
        to_sign_str=header_bs+b'.'+payload_bs
        # print(to_sign_str)
        # 如果KEY參數類型是字符串,則轉為byte類型
        if isinstance(key,str):
            key=key.encode()
        # hmac new中所有參數都需要bytes
        hmac_obj=hmac.new(key,to_sign_str,digestmod='SHA256')
        # 獲取簽名結果
        sign=hmac_obj.digest()
        # 生成sign的base64
        # sign_bs=base64.urlsafe_b64encode(sign)
        sign_bs = JWT.b64encode(sign)

        return header_bs+b'.'+ payload_bs+b'.'+sign_bs


def decode(token,key):
    '''
    # 校驗token
    # 1, 檢查簽名 【前兩項bs 再做一次hmac簽名,與第三部分進行比較,若兩者相等,校驗成功;失敗 raise】
    # 2,檢查時間戳是否過期 [過期則raise]
    # 3,return payload明文 即payload字典對象
    '''

    # 拆解token 拿出header_bs, payload_bs, sign_bs
    header_bs, payload_bs, sign_bs = token.split(b'.')
    # 判斷key的類型
    if isinstance(key, str):
        key = key.encode()

    # 重新計算簽名
    hm = hmac.new(key, header_bs + b'.' + payload_bs, digestmod='SHA256')
    # base64簽名
    new_sign = JWT.b64encode(hm.digest())
    print(new_sign)
    # 當前傳過來的token值違法則raise
    if new_sign != sign_bs:
        raise   JWTError("you token is valid")

    # 檢查payload中的時間
    payload_json = JWT.b64decode(payload_bs)
    # json字符串 ->  python對象
    payload = json.loads(payload_json)
    exp = payload['exp']
    now_t = time.time()
    if now_t > exp:
        # 過期
        raise   JWTError("you token is expired")
    return payload

class JWTError(Exception):
    '''
    自定義異常
    '''
    def __init__(self,error_msg):
        self.error=error_msg
    def __str__(self):
        return 'JWT Error error %s'%self.error


if __name__=='__main__':
    key='abcdef1234'
    res= JWT.encode({'username':'gaoxiaonao'},key=key)
    print(res)
    decode(res,key)

 

 

python 安裝與使用pyjwt

pip3 install pyjwt

方法介紹:




 1  encode(payload, key, algorithm) :

  參數說明:

  payload: jwt三大組成中的payload,需要組成字典,按需添加公有聲明和私有聲明

  例如: {'username': 'guoxiaonao', 'exp': 1562475112}

 

  參數類型: dict | token串

  返回類型:bytes 


  key : 自定義的加密key

  參數類型:str 


   algorithm: 需要使用的加密算法[HS256, RSA256等]

  參數類型:str 


2   decode(token,key,algorithm,issur="gaoxiaohui")

  token: token串

  參數類型: bytes/str | payload明文

  返回類型:dict 
   key : 自定義的加密key ,需要跟encode中的key保持一致

  參數類型:str 
  algorithm: 同encode 
   issuer: 發布者,若encode payload中添加 'iss' 字段,則可針對該字段校驗

   參數類型:str

  若iss校驗失敗,則拋出jwt.InvalidIssuerError 


  audience:簽發的受眾群體,若encode payload中添加'aud'字段,則可針對該字段校驗

  參數類型:str     若aud校驗失敗,則拋出jwt.InvalidAudienceError |

**PS**: 若encode得時候 payload中添加了exp字段; 則exp字段得值需為 當前時間戳+此token得有效期時間, 例如希望token 300秒后過期 {'exp': time.time() + 300}; 在執行decode時,若檢查到exp字段,且token過期,則拋出jwt.ExpiredSignatureError

使用實例:

mport jwt
import datetime

dic = {
'exp': datetime.datetime.now() + datetime.timedelta(days=1), # 過期時間
'iat': datetime.datetime.now(), # 開始時間
'iss': 'lianzong', # 簽名
'data': { # 內容,一般存放該用戶id和開始時間
'a': 1,
'b': 2,
},
}

s = jwt.encode(dic, 'secret', algorithm='HS256') # 加密生成字符串
print(s)
# s = jwt.decode(s, 'secret', issuer='lianzong', algorithms=['HS256']) # 解密,校驗簽名

s = jwt.decode(s, 'secret',  algorithm='HS256') # 解密,校驗簽名
print(s)
print(type(s))

 
        

結果:

b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDUzMDI5OTIsImlhdCI6MTU0NTIxNjU5MiwiaXNzIjoibGlhbnpvbmciLCJkYXRhIjp7ImEiOjEsImIiOjJ9fQ.pSq-XRcC-E7zeg3u0X6TsKdhhsCPh3tB40_YJNho8CY'
{'exp': 1545302992, 'iat': 1545216592, 'iss': 'lianzong', 'data': {'a': 1, 'b': 2}}
<class 'dict'>

先我們注意dic的結構

dic 有官方指定的key,程序在解密的時候會根據key的Value判斷是否合法。這些key有

  • “exp”: 過期時間
  • “nbf”: 表示當前時間在nbf里的時間之前,則Token不被接受
  • “iss”: token簽發者
  • “aud”: 接收者
  • “iat”: 發行時間

我們一般設置 過期時間,發行時間,接收者。我們來分別解釋這些key

exp   

exp指過期時間,在生成token時,可以設置該token的有效時間,如果我們設置1天過期,1天后我們再解析此token會拋出

jwt.exceptions.ExpiredSignatureError: Signature has expired

nbf

nbf類似於token的 lat ,它指的是該token的生效時間,如果使用但是沒到生效時間則拋出

jwt.exceptions.ImmatureSignatureError: The token is not yet valid (nbf)

iss

iss指的是該token的簽發者,我們可以給他一個字符串。

注意,iss 在接收時如果不檢驗也沒有問題,如果我們接收時需要檢驗但是又簽名不一致,則會拋出

jwt.exceptions.InvalidIssuerError: Invalid issuer

aud

aud指定了接收者,接收者在接收時必須提供與token要求的一致的接收者(字符串),如果沒寫接收者或者接收者不一致會拋出

jwt.exceptions.InvalidAudienceError: Invalid audience

iat

iat指的是token的開始時間,如果當前時間在開始時間之前則拋出

jwt.exceptions.InvalidIssuedAtError: Issued At claim (iat) cannot be in the future.

 

注意

如果我們不需要驗證所有信息直接生成token可以設置

jwt.decode(encoded, verify=False)

但是這樣有什么用呢?

 

生成/解密參數

jwt.encode(payload, config.SECRET_KEY, algorithm='HS256')

上面代碼的jwt.encode方法中傳入了三個參數:第一個是payload,這是認證依據的主要信息,第二個是密鑰,這里是讀取配置文件中的SECRET_KEY配置變量,第三個是生成Token的算法。

一般我們使用HS256

第二個參數是生成token的密鑰

我們需要在加密時指定

解密時也是第二個參數來指定解密密鑰,這兩個密鑰必須相同

 

 

 

SHA-256安全散列算法的一種

import hashlib
s = hashlib.sha256() #創建sha256對象
s.update(b'123456') #添加欲hash的內容,類型為 bytes
s.digest() #獲取最終結果 二進制
s.hexdigest() #十六進制

 

 

HMAC-SHA256 是一種通過特別計算方式之后產生的消息認證碼,使用**散列算法**同時結合一個**加密密鑰**。它可以用來保證數據的完整性,同時可以用來作某個消息的身份驗證

import hmac
#生成hmac對象
#第一個參數為加密的key,bytes類型,
#第二個參數為欲加密的串,bytes類型
#第三個參數為hmac的算法,指定為SHA256
h = hmac.new(b'danei', b'123456', digestmod='SHA256') 
h.digest() #獲取最終結果
h.hexdigest() #十六進制

 

 

base64

import base64
#base64加密
s = b'guoxiaonao'
b_s = base64.b64encode(s)
#b_s打印結果為 b'Z3VveGlhb25hbw=='

#base64解密
ss = base64.b64decode(b_s)
#ss打印結果為 b'guoxiaonao'

 

 

RSA加密算法

#生成rsa密鑰
from Crypto.PublicKey import RSA
rsa_obj = RSA.generate(1024)
private_pem = rsa_obj.exportKey() #pem格式輸出私鑰
public_key = rsa_obj.publickey()
public_pem = public_key.exportKey() #將公鑰輸出成pem格式
print public_pem

#結果類似下面這樣
'''
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8jxUVKHjb0kSYRMObVDv20IyN
A1AQS2+oHGB5LNLV+cdMttldWOZwnbHYZrYa4L/MtQhQR4e5JOZhSQe14j2RAWy+
99uXEa88upt3rpAFOjpRcN9larUPXO4yF/5KXI5eo5H2Src+K6Gu+1D1PW411Rqq
d/Uzw8zfx8q5gaH6HwIDAQAB
-----END PUBLIC KEY-----
'''

from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5
#rsa加密,通常對加密結果進行base64編碼
def encrypt(public_key, message):
    cipher = Cipher_pkcs1_v1_5.new(public_key)
    cipher_text = base64.b64encode(cipher.encrypt(message))
    return cipher_text


#rsa解密
def decrypt(rsakey, encrypt_text):
    cipher = Cipher_pkcs1_v1_5.new(rsakey)
    return cipher.decrypt(base64.b64decode(encrypt_text), '')

msg = 'hello world'
encrypt_text = encrypt(public_key, msg)
print encrypt_text

'''
goWbZ961d34RdEEgvJJtATAcAxXiY6QFTi7ToSmXQEyEKcHTNLqDdkzt3Iqwkhtfro4xCpLm4g+XqSQRNNN+3uQ9/Fahk6TZmi9eRcte5fU72jwyK6ybOAln8Chl8h14bjIsOAahmp9nuYdEFi7tV4ydNE75KMuAcHGlsJYTNjU=
'''

text = decrypt(rsa_obj, encrypt_text)
print text
'hello world'

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM