安全
AppPush同AppInterface交互,通过HTTP访问进行。在交互中,交互消息要进行身份验证和数据加密。
身份验证
客户在管理端配置app“消息推送接口及密钥”等信息时,页面会生成一个“授权码”token, 这个值用于交互双方进行身份鉴别。客户程序需要保存。交互双方在请求、响应过程中,json 体必须携带3个参数:
参数 | 类型 | 描述 |
---|---|---|
signature | 字符串 | App身份签名,signature由app的“授权码”token、时间戳timestamp、随机字符串random生成 |
timestamp | 整数(64bit) | 时间戳(1970年1月1日 00:00:00 GMT以来的毫秒数) |
random | 字符串 | 128位的UUID 通用唯一识别码 |
身份验证流程如下:
1. 将token、timestamp、random三个参数进行字典序排序;
2. 将三个参数字符串拼接成一个字符串进行sha1加密;
3. 开发者获得加密后的字符串可与signature对比,进行身份验证;
Java实现签名字符signature生成: /**
* 生成签名字符串
* @param token
* @param timestamp
* @param random
* @return
* @throws
*/
public static String genSignature ( String token,
long timestamp, String random ) throws NoSuchAlgorithmException
{
String[] arr = { token, timestamp + "", random };
//根据元素的自然顺序对指定对象数组按升序进行排序
Arrays.sort(arr);
String str = arr[0] + arr[1] + arr[2];
byte[] bt = str.getBytes();
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(bt);
return bytes2Hex(md.digest());
}
/**
* 字节数组转换成16进制字符串
* @param bts
* @return
*/
private static String bytes2Hex( byte[] bts )
{
StringBuilder des = new StringBuilder();
String tmp = null;
for (int i = 0; i < bts.length; i++)
{
tmp = (Integer.toHexString(bts[i] & 0xFF));
if (tmp.length() == 1)
des.append( "0" );
des.append( tmp );
}
return des.toString();
}
PHP实现签名字符signature生成: /**
* 生成签名字符串
* @param type $token
* @param type $timestamp
* @param type $random
* @return type
*/
public static function encodeSha1 ($token, $timestamp, $random) {
$arr = array($token, $timestamp.'', $random);
//按升序对给定数组的值排序
sort($arr);
$str = $arr[0].$arr[1].$arr[2];
return sha1($str);
}
数据加密
交互双方采用对称加密,秘钥EncodingKey由客户在管理端配置app“消息推送接口及密钥”信息时,由客户自己填写或者随机生成。这个值客户程序需要保存。由如下实例加密、解密函数,对每次交互的json体进行加、解密。加密采用AES算法(RIJNDAEL,128)。
Java实现AES算法(128位)加密、解密 /**
* AES/ECB/PKCS5Padding 加密,只支持128位(16个字节)秘钥
* 为了方便查看,对密文进行了base64编码
* @param oriStr 源字符串
* @param encryptKey 秘钥
* @return 密文
* @throws Exception
*/
public static String aesEncrypt(String oriStr, String encryptKey) throws Exception
{
if ( encryptKey == null || encryptKey.length() != 16)
throw new Exception( "secret key must 16 byte!" );
SecretKeySpec skey = new SecretKeySpec(encryptKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] crypted = cipher.doFinal(oriStr.getBytes());
return new BASE64Encoder().encode(crypted);
}
/**
* AES/ECB/PKCS5Padding 解密,只支持128位(16个字节)秘钥
* 为了方便查看,对密文进行了base64编码
* @param ciphertext 密文
* @param encryptKey 秘钥
* @return 源字符串
* @throws Exception
*/
public static String aesDecrypt(String ciphertext, String encryptKey) throws Exception
{
if ( encryptKey == null || encryptKey.length() != 16)
throw new Exception( "secret key must 16 byte!" );
SecretKeySpec skey = new SecretKeySpec(encryptKey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skey);
byte[] output = cipher.doFinal( new BASE64Decoder().decodeBuffer(ciphertext));
return new String(output);
}
Php实现AES算法(128位)加密、解密 /**
* AES/ECB/PKCS5Padding 加密,只支持128位(16个字节)秘钥
* 为了方便查看,对密文进行了base64编码
* @param type $oriStr 源字符串
* @param type $encryptKey 秘钥
* @return type 密文
*/
public static function aedEncrypt($oriStr, $encryptKey)
{
if ( strlen($encryptKey) != 16 ) {
throw new Exception("secret key must be 16 byte!");
}
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$pad = $size - (strlen($oriStr) % $size);
$oriStr = $oriStr . str_repeat(chr($pad), $pad);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $encryptKey, $iv);
$data = mcrypt_generic($td, $oriStr);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return base64_encode($data);
}
/**
* AES/ECB/PKCS5Padding 解密,只支持128位(16个字节)秘钥
* 为了方便查看,对密文进行了base64编码
* @param type $ciphertext 密文
* @param type $encryptKey 秘钥
* @return type 源字符串
*/
public static function aesDecrypt($ciphertext, $encryptKey)
{
if ( strlen($encryptKey) != 16 ) {
throw new Exception("secret key must be 16 byte : ".$encryptKey);
}
$decrypted = mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $encryptKey, base64_decode($ciphertext), MCRYPT_MODE_ECB );
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s - 1]);
return substr($decrypted, 0, -$padding);
}
AppInterface处理流程
a).AppInterface收到http请求,用秘钥EncodingKey调用函数aesDecrypt对接收的“字节流”解密。
b).解析获取到身份验证所需的signature、timestamp、random,用“授权码”token调用函数genSignature产生一个signature字符进行比较,相同则验证通过。
c).处理消息。
d).生成一个random,根据服务器的timestamp,“授权码”token调用函数genSignature生成一个signature串。
e).根据接口说明生成json,结合秘钥EncodingKey调用函数aedEncrypt生成加密的字节流,在http请求的响应中输出密文。