You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

477 lines
18 KiB
C#

1 month ago
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DS.Module.Core.Helpers
{
#region
/**
*
*/
public class DingTalkEncryptor
{
//private static readonly Charset CHARSET = Charset.forName("utf-8");
//private static readonly Base64 base64 = new Base64();
private byte[] aesKey;
private String token;
private String corpId;
/**ask getPaddingBytes key固定长度**/
private static readonly int AES_ENCODE_KEY_LENGTH = 43;
/**加密随机字符串字节长度**/
private static readonly int RANDOM_LENGTH = 16;
/**
*
* @param token token
* @param encodingAesKey EncodingAESKey
* @param corpId -, 使appKey
* -, 使corpId
* , 使suiteKey
*
* @throws DingTalkEncryptException
*/
public DingTalkEncryptor(String token, String encodingAesKey, String corpId)
{
if (null == encodingAesKey || encodingAesKey.Length != AES_ENCODE_KEY_LENGTH)
{
throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL);
}
this.token = token;
this.corpId = corpId;
aesKey = Convert.FromBase64String(encodingAesKey + "=");
}
/**
* ,Map
*/
public Dictionary<String, String> getEncryptedMap(String plaintext)
{
var time = DateTime.Now.Millisecond;
return getEncryptedMap(plaintext, time);
}
/**
* ,Map
* @param plaintext
* @param timeStamp
* @param nonce
* @return
* @throws DingTalkEncryptException
*/
public Dictionary<String, String> getEncryptedMap(String plaintext, long timeStamp)
{
if (null == plaintext)
{
throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL);
}
var nonce = Utils.getRandomStr(RANDOM_LENGTH);
if (null == nonce)
{
throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL);
}
String encrypt = this.encrypt(nonce, plaintext);
String signature = getSignature(token, timeStamp.ToString(), nonce, encrypt);
Dictionary<String, String> resultMap = new Dictionary<String, String>();
resultMap["msg_signature"] = signature;
resultMap["encrypt"] = encrypt;
resultMap["timeStamp"] = timeStamp.ToString();
resultMap["nonce"] = nonce;
return resultMap;
}
/**
*
* @param msgSignature
* @param timeStamp
* @param nonce
* @param encryptMsg
* @return
* @throws DingTalkEncryptException
*/
public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)
{
//校验签名
String signature = getSignature(token, timeStamp, nonce, encryptMsg);
if (!signature.Equals(msgSignature))
{
throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
}
// 解密
String result = decrypt(encryptMsg);
return result;
}
/*
* .
* @param text
* @return base64
*/
private String encrypt(String random, String plaintext)
{
try
{
byte[] randomBytes = System.Text.Encoding.UTF8.GetBytes(random);// random.getBytes(CHARSET);
byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plaintext);// plaintext.getBytes(CHARSET);
byte[] lengthByte = Utils.int2Bytes(plainTextBytes.Length);
byte[] corpidBytes = System.Text.Encoding.UTF8.GetBytes(corpId);// corpId.getBytes(CHARSET);
//MemoryStream byteStream = new MemoryStream();
var bytestmp = new List<byte>();
bytestmp.AddRange(randomBytes);
bytestmp.AddRange(lengthByte);
bytestmp.AddRange(plainTextBytes);
bytestmp.AddRange(corpidBytes);
byte[] padBytes = PKCS7Padding.getPaddingBytes(bytestmp.Count);
bytestmp.AddRange(padBytes);
byte[] unencrypted = bytestmp.ToArray();
RijndaelManaged rDel = new RijndaelManaged();
rDel.Mode = CipherMode.CBC;
rDel.Padding = PaddingMode.Zeros;
rDel.Key = aesKey;
rDel.IV = aesKey.ToList().Take(16).ToArray();
ICryptoTransform cTransform = rDel.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(unencrypted, 0, unencrypted.Length);
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
//Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
//SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
//IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
//cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
//byte[] encrypted = cipher.doFinal(unencrypted);
//String result = base64.encodeToString(encrypted);
//return result;
}
catch (Exception e)
{
throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR);
}
}
/*
* .
* @param text
* @return
*/
private String decrypt(String text)
{
byte[] originalArr;
try
{
byte[] toEncryptArray = Convert.FromBase64String(text);
RijndaelManaged rDel = new RijndaelManaged();
rDel.Mode = CipherMode.CBC;
rDel.Padding = PaddingMode.Zeros;
rDel.Key = aesKey;
rDel.IV = aesKey.ToList().Take(16).ToArray();
ICryptoTransform cTransform = rDel.CreateDecryptor();
originalArr = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
//return System.Text.UTF8Encoding.UTF8.GetString(resultArray);
//设置解密模式为AES的CBC模式
//Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
//SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
//IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
//cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
//使用BASE64对密文进行解码
//byte[] encrypted = Base64.decodeBase64(text);
//解密
//originalArr = cipher.doFinal(encrypted);
}
catch (Exception e)
{
throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR);
}
String plainText;
String fromCorpid;
try
{
// 去除补位字符
byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);
Console.Out.WriteLine("bytes size:" + bytes.Length);
// 分离16位随机字符串,网络字节序和corpId
byte[] networkOrder = bytes.Skip(16).Take(4).ToArray();// Arrays.copyOfRange(bytes, 16, 20);
for (int i = 0; i < 4; i++)
{
Console.Out.WriteLine("networkOrder size:" + (int)networkOrder[i]);
}
// Console.Out.WriteLine("bytes plainText:" + networkOrder.Length + " " + JsonSerializer.Serialize(networkOrder));
int plainTextLegth = Utils.bytes2int(networkOrder);
Console.Out.WriteLine("bytes size:" + plainTextLegth);
plainText = System.Text.UTF8Encoding.UTF8.GetString(bytes.Skip(20).Take(plainTextLegth).ToArray()); // new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET);
fromCorpid = System.Text.UTF8Encoding.UTF8.GetString(bytes.Skip(20 + plainTextLegth).ToArray()); //new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);
Console.Out.WriteLine("bytes plainText:" + plainText);
}
catch (Exception e)
{
throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR);
}
Console.Out.WriteLine(fromCorpid + "=====" + corpId);
// corpid不相同的情况
if (!fromCorpid.Equals(corpId))
{
throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR);
}
return plainText;
}
/**
*
* @param token isv token
* @param timestamp
* @param nonce
* @param encrypt
* @return
* @throws DingTalkEncryptException
*/
public String getSignature(String token, String timestamp, String nonce, String encrypt)
{
try
{
Console.Out.WriteLine(encrypt);
String[] array = new String[] { token, timestamp, nonce, encrypt };
System.Array.Sort(array, StringComparer.Ordinal);
//var tmparray = array.ToList();
//tmparray.Sort(new JavaStringComper());
//array = tmparray.ToArray();
// Console.Out.WriteLine("array:" + JsonSerializer.Serialize(array));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; i++)
{
sb.Append(array[i]);
}
String str = sb.ToString();
Console.Out.WriteLine(str);
//MessageDigest md = MessageDigest.getInstance("SHA-1");
//md.update(str.getBytes());
//byte[] digest = md.digest();
System.Security.Cryptography.SHA1 hash = System.Security.Cryptography.SHA1.Create();
System.Text.Encoding encoder = System.Text.Encoding.ASCII;
byte[] combined = encoder.GetBytes(str);
// byte 转换
//sbyte[] myByte = new sbyte[]
//byte[] mySByte = new byte[myByte.Length];
//for (int i = 0; i < myByte.Length; i++)
//{
// if (myByte[i] > 127)
// mySByte[i] = (sbyte)(myByte[i] - 256);
// else
// mySByte[i] = (sbyte)myByte[i];
//}
byte[] digest = hash.ComputeHash(combined);
StringBuilder hexstr = new StringBuilder();
String shaHex = "";
for (int i = 0; i < digest.Length; i++)
{
shaHex = ((int)digest[i]).ToString("x");// Integer.toHexString(digest[i] & 0xFF);
if (shaHex.Length < 2)
{
hexstr.Append(0);
}
hexstr.Append(shaHex);
}
return hexstr.ToString();
}
catch (Exception e)
{
throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
}
}
}
/**
*
*/
public class DingTalkEncryptException : Exception
{
/**成功**/
public static readonly int SUCCESS = 0;
/**加密明文文本非法**/
public readonly static int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001;
/**加密时间戳参数非法**/
public readonly static int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002;
/**加密随机字符串参数非法**/
public readonly static int ENCRYPTION_NONCE_ILLEGAL = 900003;
/**不合法的aeskey**/
public readonly static int AES_KEY_ILLEGAL = 900004;
/**签名不匹配**/
public readonly static int SIGNATURE_NOT_MATCH = 900005;
/**计算签名错误**/
public readonly static int COMPUTE_SIGNATURE_ERROR = 900006;
/**计算加密文字错误**/
public readonly static int COMPUTE_ENCRYPT_TEXT_ERROR = 900007;
/**计算解密文字错误**/
public readonly static int COMPUTE_DECRYPT_TEXT_ERROR = 900008;
/**计算解密文字长度不匹配**/
public readonly static int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009;
/**计算解密文字corpid不匹配**/
public readonly static int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010;
private static Dictionary<int, String> msgMap = new Dictionary<int, String>();
static DingTalkEncryptException()
{
msgMap[SUCCESS] = "成功";
msgMap[ENCRYPTION_PLAINTEXT_ILLEGAL] = "加密明文文本非法";
msgMap[ENCRYPTION_TIMESTAMP_ILLEGAL] = "加密时间戳参数非法";
msgMap[ENCRYPTION_NONCE_ILLEGAL] = "加密随机字符串参数非法";
msgMap[SIGNATURE_NOT_MATCH] = "签名不匹配";
msgMap[COMPUTE_SIGNATURE_ERROR] = "签名计算失败";
msgMap[AES_KEY_ILLEGAL] = "不合法的aes key";
msgMap[COMPUTE_ENCRYPT_TEXT_ERROR] = "计算加密文字错误";
msgMap[COMPUTE_DECRYPT_TEXT_ERROR] = "计算解密文字错误";
msgMap[COMPUTE_DECRYPT_TEXT_LENGTH_ERROR] = "计算解密文字长度不匹配";
msgMap[COMPUTE_DECRYPT_TEXT_CORPID_ERROR] = "计算解密文字corpid不匹配";
}
private int code;
public DingTalkEncryptException(int exceptionCode) : base(msgMap[exceptionCode])
{
this.code = exceptionCode;
}
}
/*
* PKCS7
*/
public class PKCS7Padding
{
//private readonly static Charset CHARSET = Charset.forName("utf-8");
private readonly static int BLOCK_SIZE = 32;
/**
* mode
* @param count
* @return
*/
public static byte[] getPaddingBytes(int count)
{
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0)
{
amountToPad = BLOCK_SIZE;
}
char padChr = chr(amountToPad);
String tmp = string.Empty; ;
for (int index = 0; index < amountToPad; index++)
{
tmp += padChr;
}
return System.Text.Encoding.UTF8.GetBytes(tmp);
}
/**
* mode
* @param decrypted
* @return
*/
public static byte[] removePaddingBytes(byte[] decrypted)
{
int pad = (int)decrypted[decrypted.Length - 1];
if (pad < 1 || pad > BLOCK_SIZE)
{
pad = 0;
}
//Array.Copy()
var output = new byte[decrypted.Length - pad];
System.Array.Copy(decrypted, output, decrypted.Length - pad);
return output;
}
private static char chr(int a)
{
byte target = (byte)(a & 0xFF);
return (char)target;
}
}
/**
*
*/
public class Utils
{
/**
*
* @return
*/
public static String getRandomStr(int count)
{
String baset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++)
{
int number = random.Next(baset.Length);
sb.Append(baset[number]);
}
return sb.ToString();
}
/*
* intbyte,
*/
public static byte[] int2Bytes(int count)
{
byte[] byteArr = new byte[4];
byteArr[3] = (byte)(count & 0xFF);
byteArr[2] = (byte)(count >> 8 & 0xFF);
byteArr[1] = (byte)(count >> 16 & 0xFF);
byteArr[0] = (byte)(count >> 24 & 0xFF);
return byteArr;
}
/**
* bytesint
* @param byteArr
* @return
*/
public static int bytes2int(byte[] byteArr)
{
int count = 0;
for (int i = 0; i < 4; ++i)
{
count <<= 8;
count |= byteArr[i] & 255;
}
return count;
}
}
public class JavaStringComper : IComparer<string>
{
public int Compare(string x, string y)
{
return String.Compare(x, y);
}
}
#endregion
}