生成ACCESS_TOKEN进行http 请求

生成访问服务器API的必须的ACCESS_TOKEN

用标准的http POST和GET请求就可以调用mixin 网络的API。开发者需要根据JWT协议来生成一个使用RSA算法的ACCESS_TOKEN。然后将ACCESS_TOKEN放入http header。

生成ACCESS_TOKEN的JWT输入参数如下:

"uid": App ID(the one in UUID format, not the 7000xxxx),
"sid": session ID,
"iat": current ts time,
"exp": 过期时间(200 means expired afte 200 seconds),
"jti": unique id in uuid format,
"sig": sha256(method+URI+body),
Calculate in python
jwtSig = hashlib.sha256(method + uri+body).hexdigest()
iat = datetime.datetime.utcnow()
exp = datetime.datetime.utcnow() + datetime.timedelta(seconds=200)
jwtResult = jwt.encode({'uid': mixin_client_id, 'sid':mixin_sessionid,'iat':iat,'exp': exp, 'jti':str(uuid.uuid1()),'sig':jwtSig}, private_key, algorithm='RS512')

然后把ACCESS_TOKEN 放入http header 里面, 类似 "Authorization": "Bearer ACCESS_TOKEN"

Http post in python
encoded = robot.genPOSTJwtToken_extConfig('/transfers', body_in_json, config)
r = requests.post('https://api.mixin.one/transfers', json = body, headers = {"Authorization":"Bearer " + encoded})

额外的资产保护机制

当使用转账等API时,开发者需要提供机器人的资产pin码。这个pin码需要与其他内容合并在一起,经过加密放在http请求的body里面。

需要加密的内容是 : pin + 时间戳(little endian) + 8 byte 计数器

时间戳以小端形式存放。计数器是一个自动自增的计数器。用户的每次API调用使用的计数器必须比原来更大。

加密算法是AES

用于加密的AES key可以从机器人或者用户的pin_token解密而来。

在开发者中心里面有一个参数pin_token,这是一个经过加密的AES 密钥。为了保证安全传输,服务器使用用户/机器人的公钥加密,然后将加密结果进行base64编码。为了计算出原本的AES密钥,开发者需要先把结果用base64格式解码,然后使用自己的私钥解密。here

Go代码如下

Go-generate-encrypted-pin
// Golang Example Code
func EncryptPIN(pin, pinToken, sessionId, privateKey string, iterator uint64) string {
keyBlock, _ := pem.Decode([]byte(privateKey))
key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
if err != nil {
return ""
}
token, _ := base64.StdEncoding.DecodeString(pinToken)
keyBytes, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, key, token, []byte(sessionId))
if err != nil {
return ""
}
pinByte := []byte(pin)
timeBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(timeBytes, uint64(time.Now().Unix()))
pinByte = append(pinByte, timeBytes...)
iteratorBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(iteratorBytes, iterator)
pinByte = append(pinByte, iteratorBytes...)
padding := aes.BlockSize - len(pinByte)%aes.BlockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
pinByte = append(pinByte, padtext...)
block, _ := aes.NewCipher(keyBytes)
ciphertext := make([]byte, aes.BlockSize+len(pinByte))
iv := ciphertext[:aes.BlockSize]
io.ReadFull(rand.Reader, iv)
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], pinByte)
return base64.StdEncoding.EncodeToString(ciphertext)
}

Python 代码如下

generate encrypted pin in python
def genEncrypedPin_extConfig(self, ext_config):
privKeyObj = RSA.importKey(ext_config.private_key)
decoded_result = base64.b64decode(ext_config.mixin_pin_token)
decoded_result_inhexString = ":".join("{:02x}".format(ord(c)) for c in decoded_result)
cipher = PKCS1_OAEP.new(key = privKeyObj, hashAlgo = Crypto.Hash.SHA256, label = ext_config.mixin_pay_sessionid)
decrypted_msg = cipher.decrypt(decoded_result)
decrypted_msg_inhexString = ":".join("{:02x}".format(ord(c)) for c in decrypted_msg)
keyForAES = decrypted_msg
ts = int(time.time())
tszero = ts%0x100
tsone = (ts%0x10000) >> 8
tstwo = (ts%0x1000000) >> 16
tsthree = (ts%0x100000000) >> 24
tsstring = chr(tszero) + chr(tsone) + chr(tstwo) + chr(tsthree) + '\0\0\0\0'
counter = '\1\0\0\0\0\0\0\0'
toEncryptContent = ext_config.asset_pin + tsstring + tsstring
lenOfToEncryptContent = len(toEncryptContent)
toPadCount = 16 - lenOfToEncryptContent % 16
if toPadCount > 0:
paddedContent = toEncryptContent + chr(toPadCount) * toPadCount
else:
paddedContent = toEncryptContent
iv = Random.new().read(AES.block_size)
cipher = AES.new(keyForAES, AES.MODE_CBC, iv)
encrypted_result = cipher.encrypt(paddedContent)
msg = iv + encrypted_result
encrypted_pin = base64.b64encode(msg)
return encrypted_pin

计算出Encrypted_pin之后将结果放到http请求的body里面,如下

Http post to transfer asset in python
body = {'asset_id': to_asset_id, 'counter_user_id':to_user_id, 'amount':str(to_asset_amount), 'pin':encrypted_pin, 'trace_id':str(uuid.uuid1()), 'memo':memo}
body_in_json = json.dumps(body)
encoded = robot.genPOSTJwtToken_extConfig('/transfers', body_in_json, config)
r = requests.post('https://api.mixin.one/transfers', json = body, headers = {"Authorization":"Bearer " + encoded})