车轮——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'

 

posted @ 2020-05-04 15:24  岁月荏苒¥我心依旧  阅读(1622)  评论(0编辑  收藏  举报