中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

NodeJS開發釘釘回調接口實現AES-CBC加解密

發布時間:2020-08-21 10:02:56 來源:億速云 閱讀:378 作者:小新 欄目:開發技術

這篇文章主要介紹NodeJS開發釘釘回調接口實現AES-CBC加解密,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

釘釘小程序后臺接收釘釘開放平臺的回調比較重要,比如通訊錄變動的回調,審批流程的回調都是在業務上十分需要的。回調接口時打通釘釘平臺和內部系統的重要渠道。

但是給回調的接口增加了一些障礙,它需要支持回調的服務器的接口支持AES-CBC加解密。不然無法成功注冊或解析內容。

釘釘官方文檔中給出了JAVA,PHP,C#的后臺SDK和demo,但是卻沒有Node服務器的代碼支持,這讓占有率很高的node服務器非常尷尬,難道node就不能作為釘釘平臺的回調服務器么

NodeJS開發釘釘回調接口實現AES-CBC加解密

好在釘釘已經開放了其加密算法,可以通過加密流程自己寫一套JavaScript版的加解密程序,然后將node服務器注冊為釘釘的回調接口。

首先,看一下釘釘回調接口的注冊流程

NodeJS開發釘釘回調接口實現AES-CBC加解密

首先,是由開發者主動發起一個POST請求到釘釘開放平臺,傳過去回調的URL,然后釘釘在這個請求中返回一個ok,如下圖

NodeJS開發釘釘回調接口實現AES-CBC加解密

在這里,我申請了通訊錄加人或修改人事件的回調。

在這個接口請求完畢之后,釘釘會迅速的向你請求參數中寫的url發送一個POST請求,如下

{"encrypt":"ihVRgn3eZZrCYHfAW4Lbh9eoOcpy1VddxGS9IIYsteFgAxpPN9ZaKKp4EH/7ArtmVEACxmyGCdUFtGuXxfNfcbXXXXXXXXXXXXXXXXXXXkGy+Oq/hIN"}

此時,釘釘要求我們“success”加密,然后在服務器中響應。

AES是一種對稱性加密,即加密者通過一個密鑰進行加密,將密文發送給接收人,接收人通過相同的密鑰進行解密。但是CBC這種模式下,還需要一個偏移,或者說IV向量進行加解密。所以在加解密的時候實際上需要兩個參數,密鑰和IV。換句話說,釘釘回調接口使用的加密方式為AES-256-CBC模式

按照文檔要求,我們返回的JSON中需要包含4個字段

NodeJS開發釘釘回調接口實現AES-CBC加解密

其中,nonce是可以隨便寫的字符串,長度也沒有限制,是用來增加msg_signature的變化度的。

timeStamp是10位數的時間戳,JavaScript默認時間戳是13位的,我們需要除以1000或者截取后3位。

encrypt是一段base64編碼后的字符串,被編碼的是“sucess”被加密后的密文

msg_signature是一段hash值,是將其余3個字符串,加上我們注冊接口時設定的自定義token,4個字符串排序好,通過SHA1算法HASH后的值,用來驗證完整性的。具體如下

NodeJS開發釘釘回調接口實現AES-CBC加解密

最難以解決的就是encrypt字段了,還好在JS界谷歌已經給我們準備好了CryptoJS庫,不用幾行代碼就可以解決問題。

首先觀察下這個encrypt字段的形成邏輯:

NodeJS開發釘釘回調接口實現AES-CBC加解密

需要被加密的明文由四個部分組成,分別是

16個字節的隨機字符串:ASCII編碼中,一個字符就占1個字節(8位),所以這里我們隨便填16個字母組成的字符串就行

4個字節的msg長度:這里的長度不是文本格式的長度,而是4*8=32位二進制表示的長度,文檔中沒有明確指出是填msg的字節長度,還是比特位數,通過我個人驗證,此處應該填msg的字節數。由于"success"由7個ASCII字符組成,所以長度為7,以4個字節的二進制表示就是

00000000 00000000 00000000 00000111

在JS中,要想把二進制數轉化成字節,可以先換成十進制,然后使用String.fromCharCode(0)方法,轉換為字節。所以此處要想用字符串表示,就是把0,0,0,7當作ASCII碼轉換為不可見字符

var lengthString = String.fromCharCode(0)+String.fromCharCode(0)+String.fromCharCode(0)+String.fromCharCode(7)

明文msg:就是字符串"success"

$key:我是企業內部開發,Corpid可以在釘釘開發者后臺看到

有了明文,下一步就是進行加密

首先我們知道AES-CBC算法需要一個密鑰KEY和一個偏移量IV,而釘釘說IV是密鑰的前16位,如下

NodeJS開發釘釘回調接口實現AES-CBC加解密

釘釘讓我們提供32字節長的密鑰,換句話說就是256比特,然后把密鑰Base64進行編碼,通過上面的注冊接口發給釘釘。

由于一個ASCII字符就是一個字節,所以我們這里生成一個32字符長度的字符串,就由密鑰了,我選擇的密鑰是"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",因為@字符的ASCII碼值是64,容易記,同理,IV就是16個@組成的字符串。

注意32字節的長度的字符串base64編碼后,長度肯定位44個字符,最后一位必然是=,去掉等號就是43個字符了

通過使用10進制的數字,轉換為Byte字符串,也可以通過數組來解決,如上面這個,我就可以通過下面代碼來生成密鑰

var key_256 = [64, 64, 64, 64, 64, 64, 64, 64, 
        64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64,
        64, 64, 64, 64, 64, 64, 64, 64];
var key_text = '';
for(let i=0;i<32;i++){
	key_text += String.fromCharCode(key_256[i]);
}
console.log(btoa(key_text))

通過JS的btoa()函數,可以直接把密鑰變成Base64格式

同理,生成IV之后,就可以開始進行加密操作了,這里直接放出代碼

CryptoJS庫既可以在HTML中使用,也可以require到node中使用

在HTML中使用時,先到https://code.google.com/archive/p/crypto-js/downloads下載最新壓縮包,然后解壓到項目目錄即可,如下

NodeJS開發釘釘回調接口實現AES-CBC加解密

然后再HTML中進行引用

<script src = "crypto-js-4.0.0/crypto-js.js"></script>

這樣我們就可以直接通過瀏覽器本地調試,生成我們想要的字符串,讓node服務器直接原文返回就可以了

<html>
<head>
<script src = "crypto-js-4.0.0/crypto-js.js"></script>
<script>

 
// AES 秘鑰
var AesKey = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@";
console.log(btoa(AesKey))


// AES-128-CBC偏移量
var CBCIV = "@@@@@@@@@@@@@@@@";


//16個字節的隨機字符串
var randomString = '1234567890123456';
//明文msg
var msg = 'success';
//$key,對于企業內部開發來說,$key填寫企業的Corpid。
var corpid = 'ding00000035b90000000005d6980864d335'

function len_msg(msg){//該函數返回的是字符串,無文本意義
	result = String.fromCharCode(0)+String.fromCharCode(0)+String.fromCharCode(0)+String.fromCharCode(7);
	return result;
}
//msg_len(4B),此處為ASCII編碼的二進制字符串,無文本意義
var msg_len = len_msg(msg);
//要加密的明文是[random(16B) + msg_len(4B) + msg + $key]
var codeString = randomString + msg_len + msg + corpid;

		 
console.log('要加密的明文字符串為:'+codeString);
console.log('要加密的字符串Base64為:'+btoa(codeString));
 
 
// 加密選項
var CBCOptions = {
	iv: CryptoJS.enc.Latin1.parse(CBCIV),
	mode:CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
}
 
/**
 * AES加密(CBC模式,需要偏移量)
 * @param data
 * @returns {*}
 */
function encrypt(data){
  var key = CryptoJS.enc.Latin1.parse(AesKey);
  var secretData = CryptoJS.enc.Latin1.parse(data);
  var encrypted = CryptoJS.AES.encrypt(
		secretData, 
		key, 
		CBCOptions
	);
  return encrypted.toString();
}
/**
 * AES解密(CBC模式,需要偏移量)
 * @param data
 * @returns {*}
 */
function decrypt(data){
  var key = CryptoJS.enc.Latin1.parse(AesKey);
  var decrypt = CryptoJS.AES.decrypt(
		data, 
		key, 
		CBCOptions
	);
  return CryptoJS.enc.Latin1.stringify(decrypt).toString();
}


//encrypt = Base64_Encode(AES_Encrypt[random(16B) + msg_len(4B) + msg + $key])
var encodeData=encrypt(codeString);
console.log('加密后密文為:'+encodeData);
console.log('10位時間戳:'+parseInt(new Date()/1000));

var timeStamp = ""+parseInt(new Date()/1000);
 var nonce = "aaaaaa";
 var encrypt = "LwJ0000000000000000000000000000000000000YYQIBxRvsQ=="
 var token = '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@';
 
 //dev_msg_signature=sha1(sort(token、timestamp、nonce、msg_encrypt))
 var sortList = [timeStamp,nonce,encrypt,token];
 sortList.sort();
 console.log(sortList);
 
 var msg_signature = '';
 for (i in sortList){
	msg_signature += i;
 }
 console.log(msg_signature);
 console.log(CryptoJS.SHA1(msg_signature).toString())
 
var secretTxt = 'Fuqa0wgIvMtUgFBnyZkCb1z3tpSYJ0000000000000000000000p64KnDZkGsjP3y5AIGnryUjkMi16Lz5C/ZzkMRbaipIgz60U5gELKSblZ3MnTf1CVbPMvyjoYbyenjbKCDmQpdgdA4Ejh8Cnlil1laZ8wQSUSD0ju8a9pFIx9Rh7HwNfh0FenpnX22HpfU000007ZjNM5PeK5DeCbmCrqnrq1zwjqomeXSw8mw9g0i83DQKYMXuU3KsO000cHPLdfbWIKUyTcw=='
var realMessage = decrypt(secretTxt);
console.log('實際內容是'+realMessage);
</script>
</head>
<body>
</body>
</html>

注意,加密選項中CryptoJS.enc.Latin1.parse(AesKey);是將字符串表示的密鑰通過ASCII碼轉換為字節,在加密時也可以使用CryptoJS.enc.Utf8,因為utf8編碼再ASCII字符中編碼沒有區別。

但是反過來,加密中用Latin1和Utf8都沒有問題,但是在解密時,釘釘那邊是使用ASCII編碼的,如果使用CryptoJS.enc.Utf8就會發生錯誤。因為釘釘返回內容應該全是普通英文字符,沒有中文或其他特殊字符

對于消息體簽名,我們只需使用JS的arr.sort(),把四個字段組成的數組通過首字母進行排序,然后首尾相連變為一個字符串,再使用CryptoJS.SHA1(msg_signature).toString()的SHA1算法取HASH值即可,注意這里的HASH值是HEX格式表示的(文檔沒有寫,但是通過實驗得出的),不要用Base64了,代碼上等價于

CryptoJS.SHA1(msg_signature).toString(CryptoJS.enc.Hex);

還有encrypted.toString()方法,默認返回的就是Base64編碼格式,無需轉換,這一點和上面SHA1方法的默認值不同,還有,CryptoJS.AES.decrypt()方法,傳入的待解碼的密文,也可以直接把釘釘給的Base64格式密文傳入的,無需提前解碼Base64

注意,排序四元素之一的token,既不是AES的密鑰,也不是IV,也不是釘釘平臺的access_token,而是我們在前面https://oapi.dingtalk.com/call_back/register_call_back接口中上傳的token字段,是個純自定義的的字段

我們通過在瀏覽器中執行上面的代碼,就可以把注冊回調需要返回的JSON值都獲取到,然后我們直接在node里寫死這幾個值用來返回就可以了,同時,我們還需要在nodejs中引入CryptoJS,用來對釘釘發來的回調信息進行解密

const express = require('express')
const bodyParser = require('body-parser');
const CryptoJS = require("crypto-js");


const app = express()
const port = 8080
const appkey = 'dingxxxx';
const appsecret = 'xxxxxx';
const agentId = 'xxxxxx';

var dingToken = '';

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, function(){ 
	console.log(`Example app listening on port ${port}!`);
	getToken();
})

app.use(bodyParser.json())

app.post('/dingCallback', function (req, res) {
 console.log('釘釘回調接口收到請求了:'+JSON.stringify(req.body));//獲取釘釘的回調參數
 
 var timeStamp = ""+parseInt(new Date()/1000);//動態項
 var nonce = "aaaaaa";//隨便寫
 var encrypt = "LwXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxRvsQ=="
 var token = '666666';
 
 //dev_msg_signature=sha1(sort(token、timestamp、nonce、msg_encrypt))
 var sortList = [timeStamp,nonce,encrypt,token];
 sortList.sort();
 console.log(sortList);
 
 var msg_signature = '';
 for (let text of sortList){
	msg_signature += text;
 }
 console.log('msg_signature明文='+msg_signature)
 msg_signature = CryptoJS.SHA1(msg_signature).toString()

 
 var resp = {
	msg_signature:msg_signature,
	timeStamp:timeStamp,
	nonce:nonce,
	encrypt:encrypt
 }
 console.log(''+JSON.stringify(resp))
 
 console.log('解密內容是:'+decryptMsg(req.body.encrypt));//獲取釘釘傳過來的參數,并解密處json信息
 res.send(JSON.stringify(resp));
 
});

// AES 秘鑰
var AesKey = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@";

// AES-128-CBC偏移量
var CBCIV = "@@@@@@@@@@@@@@@@";

// 加密選項
var CBCOptions = {
	iv: CryptoJS.enc.Latin1.parse(CBCIV),
	mode:CryptoJS.mode.CBC,
	padding: CryptoJS.pad.Pkcs7
}

/**
 * AES解密(CBC模式,需要偏移量)
 * @param data Base64格式
 * @returns {*}
 */
function decrypt(data){
  var key = CryptoJS.enc.Latin1.parse(AesKey);
  var decrypt = CryptoJS.AES.decrypt(
		data, 
		key, 
		CBCOptions
	);
  return CryptoJS.enc.Latin1.stringify(decrypt).toString();
}

function decryptMsg(base64_crypt_msg){
	var realMessage = decrypt(base64_crypt_msg);
	var endPosition = realMessage.lastIndexOf('dingXXXXXXXX');//掐頭去尾,前面掐掉20字節,后面掐掉Corpid
	if(!realMessage || realMessage.length < 20 || endPosition==0){
		console.log('解密失敗')
		return;
	}
	var jsonData = realMessage.slice(20,endPosition);
	return jsonData;
}

釘釘用于驗證你服務器的POST請求,與給你發信息的回調參數,格式是一樣的,POST收到的明文為:

{"encrypt":"ihVRgn3eZZrCYHfAW4Lbh9eoOcpy1VddxGS9IIYsteFgAxpPN9ZaKKp4EH/7ArtmV"}

 解密之后,密文部分為一個JSON字符串,里面包含著我們想要的東西,如,用于驗證url的參數解密后為,這個和我們設置的響應加密字符串一樣,是16字節的隨機字符串,4個字節的二進制長度,正文+Corpid。

AzW30dHltl1iocOd{"EventType":"check_url"}dingxxxxxxxxxxxxxxxxxxxxxxx

要判斷釘釘回調我們的接口是否成功,或者說我們有沒有返回正確的加密報文,只需調用釘釘的查看回調接口列表就行了,方法是使用POST請求調用https://oapi.dingtalk.com/call_back/get_call_back&#63;access_token=,然后觀察回調接口中是否包含你剛注冊的url即可

NodeJS開發釘釘回調接口實現AES-CBC加解密

另外推薦一個網站,可以將base64后的待加密字符串,使用AES-256-CBC算法進行加解密

https://the-x.cn/cryptography/Aes.aspx

NodeJS開發釘釘回調接口實現AES-CBC加解密

以上是NodeJS開發釘釘回調接口實現AES-CBC加解密的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

芷江| 永嘉县| 腾冲县| 建始县| 沅江市| 山东省| 陆川县| 林甸县| 石楼县| 施甸县| 新沂市| 六枝特区| 武威市| 泸定县| 稷山县| 韶山市| 潞西市| 米脂县| 育儿| 淮北市| 双桥区| 广南县| 玉树县| 娄烦县| 乌拉特中旗| 江阴市| 怀仁县| 浑源县| 台北县| 扶绥县| 集贤县| 南投县| 荔浦县| 阿巴嘎旗| 体育| 娄底市| 望江县| 聂荣县| 磐安县| 马龙县| 兰溪市|