互联网API开放平台安全设计-接口安全加密传输对称加密与非对称加密
Posted 漫漫求索修身
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了互联网API开放平台安全设计-接口安全加密传输对称加密与非对称加密相关的知识,希望对你有一定的参考价值。
互联网开放平台设计
1.需求:现在A公司与B公司进行合作,B公司需要调用A公司开放的外网接口获取数据,
如何保证外网开放接口的安全性。
2.常用解决办法:
2.1 使用加签名方式,防止篡改数据
2.2 使用Https加密传输
2.3 搭建OAuth2.0认证授权
2.4 使用令牌方式
2.5 搭建网关实现黑名单和白名单
URL转码
什么是URL转码
不管是以何种方式传递url时,如果要传递的url中包含特殊字符,如想要传递一个+,但是这个+会被url会被编码成空格,想要传递&,被url处理成分隔符。
尤其是当传递的url是经过Base64加密或者RSA加密后的,存在特殊字符时,这里的特殊字符一旦被url处理,就不是原先你加密的结果了。
url特殊符号及对应的编码:
符号 |
url中的含义 |
编码 |
+ |
URL 中+号表示空格 |
%2B |
空格 |
URL中的空格可以用+号或者编码 |
%20 |
/ |
分隔目录和子目录 |
%2F |
? |
分隔实际的URL和参数 |
%3F |
% |
指定特殊字符 |
%25 |
# |
表示书签 |
%23 |
& |
URL中指定的参数间的分隔符 |
%26 |
= |
URL中指定参数的值 |
%3D |
URLEncode和URLDecode
接受参数案例tranIndex
@RestController public class TranController { // 接受客户端参数 @RequestMapping("/tranIndex") public String tranIndex(String name) { System.out.println("name:" + name); return name; } } |
客户端访问结果
传入+参数变为了空格。
解决办法:将+变为%2B
Java代码处理转码
URLEncoder.encode和decode
String encode = URLEncoder.encode("1+1", "UTF-8"); String decode = URLDecoder.decode(encode, "UTF-8"); System.out.println("encode:" + encode + ",decode:" + decode); |
Http接口参数编码处理
String url = "http://127.0.0.1:8080/tranIndex?"; // 参数转码 String strParam = "name=" + URLEncoder.encode("1+1", "utf-8"); String newUrl = url + strParam; String result = HttpClientUtils.httpGet(newUrl); System.out.println("result:" + result); |
信息加密与密钥管理
单向散列加密
散列是信息的提炼,通常其长度要比信息小得多,且为一个固定长度。加密性强的散列一定是不可逆的,这就意味着通过散列结果,无法推出任何部分的原始信息。任何输入信息的变化,哪怕仅一位,都将导致散列结果的明显变化,这称之为雪崩效应。散列还应该是防冲突的,即找不出具有相同散列结果的两条信息。具有这些特性的散列结果就可以用于验证信息是否被修改。
单向散列函数一般用于产生消息摘要,密钥加密等,常见的有:
1、MD5(Message Digest Algorithm 5):是RSA数据安全公司开发的一种单向散列算法,非可逆,相同的明文产生相同的密文。
2、SHA(Secure Hash Algorithm):可以对任意长度的数据运算生成一个160位的数值;
SHA-1与MD5的比较
因为二者均由MD4导出,SHA-1和MD5彼此很相似。相应的,他们的强度和其他特性也是相似,但还有以下几点不同:
1、对强行供给的安全性:最显著和最重要的区别是SHA-1摘要比MD5摘要长32 位。使用强行技术,产生任何一个报文使其摘要等于给定报摘要的难度对MD5是2128数量级的操作,而对SHA-1则是2160数量级的操作。这样,SHA-1对强行攻击有更大的强度。
2、对密码分析的安全性:由于MD5的设计,易受密码分析的攻击,SHA-1显得不易受这样的攻击。
3、速度:在相同的硬件上,SHA-1的运行速度比MD5慢。
1、特征:雪崩效应、定长输出和不可逆。
2、作用是:确保数据的完整性。
3、加密算法:md5(标准密钥长度128位)、sha1(标准密钥长度160位)、md4、CRC-32
4、加密工具:md5sum、sha1sum、openssl dgst。
5、计算某个文件的hash值,例如:md5sum/shalsum FileName,openssl dgst –md5/-sha
MD5加密
在线MD5解密与加密
http://www.cmd5.com/
Java操作MD5加密
MD5加盐实现方式
一般使用的加盐:
md5(Password+UserName),即将用户名和密码字符串相加再MD5,这样的MD5摘要基本上不可反查。
但有时候用户名可能会发生变化,发生变化后密码即不可用了(验证密码实际上就是再次计算摘要的过程)。
----------
因此我们做了一个非常简单的加盐算法,每次保存密码到数据库时,都生成一个随机16位数字,将这16位数字和密码相加再求MD5摘要,然后在摘要中再将这16位数字按规则掺入形成一个48位的字符串。
在验证密码时再从48位字符串中按规则提取16位数字,和用户输入的密码相加再MD5。按照这种方法形成的结果肯定是不可直接反查的,且同一个密码每次保存时形成的摘要也都是不同的。
代码如下:
package com.cy.utils; import java.security.MessageDigest; import java.util.Random; import org.apache.commons.codec.binary.Hex; public class MD5Utils { /** * 生成含有随机盐的密码 */ public static String generate(String password) { Random r = new Random(); StringBuilder sb = new StringBuilder(16); sb.append(r.nextInt(99999999)).append(r.nextInt(99999999)); int len = sb.length(); if (len < 16) { for (int i = 0; i < 16 - len; i++) { sb.append("0"); } } String salt = sb.toString(); password = md5Hex(password + salt); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = password.charAt(i / 3 * 2); char c = salt.charAt(i / 3); cs[i + 1] = c; cs[i + 2] = password.charAt(i / 3 * 2 + 1); } return new String(cs); } /** * 校验密码是否正确 */ public static boolean verify(String password, String md5) { char[] cs1 = new char[32]; char[] cs2 = new char[16]; for (int i = 0; i < 48; i += 3) { cs1[i / 3 * 2] = md5.charAt(i); cs1[i / 3 * 2 + 1] = md5.charAt(i + 2); cs2[i / 3] = md5.charAt(i + 1); } String salt = new String(cs2); return md5Hex(password + salt).equals(new String(cs1)); } /** * 获取十六进制字符串形式的MD5摘要 */ public static String md5Hex(String src) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] bs = md5.digest(src.getBytes()); return new String(new Hex().encode(bs)); } catch (Exception e) { return null; } } public static void main(String[] args) { // 加密+加盐 String password1 = generate("admin"); System.out.println("结果:" + password1 + " 长度:" + password1.length()); // 解码 System.out.println(verify("admin", password1)); // 加密+加盐 String password2 = generate("admin"); System.out.println("结果:" + password2 + " 长度:" + password2.length()); // 解码 System.out.println(verify("admin", password2)); } } |
信息加密技术
对称加密
对称密码技术:发件人和收件人使用其共同拥有的单个密钥 ,这种密钥既用于加密,也用于解密,叫做机密密钥(也称为对称密钥或会话密钥)。
能够提供信息机密性(没有密钥信息不能被解密)、完整性(被改变的信息不能被解密)的服务。
对称式密码学又称:单钥密码学、秘密密钥密码学、会话密钥密码学、私钥密码学、共享秘钥密码学
常见的对称式加密技术
DES(数据加密标准):分组式加密,算法源于Lucifer,作为NIST对称式加密标准;64位(有效位56位、校验8位),分组算法
3DES:128位,分组算法
IDEA(国际数据加密算法):128位,比DES快,分组算法
Blowfish:32-448位,算法公开,分组算法
RC4:流密码,密钥长度可变
RC5:分组密码,密钥长度可变,最大2048位
Rijndael:128位/196位/256位
AES(高级加密标准):DES升级版,算法出自Rinjindael
对称密码的优点
用户只需记忆一个密钥,就可用于加密、解密;
与非对称加密方法相比,加密解密的计算量小,速度快,简单易用,适合于对海量数据进行加密处理 。
对称密码的缺点
如果密钥交换不安全,密钥的安全性就会丧失。特别是在电子商务环境下,当客户是未知的、不可信的实体时,如何使客户安全地获得密钥就成为一大难题。
如果用户较多情况下的密钥管理问题。N*(N-1)/2
如果密钥多个用户被共享,不能提供抗抵赖性
对称密码案例
假设Alice和Bob是认识的,两人为了保证通信消息不被其它人截取,预先约定了一个密码,用来加密在他们之间传送的消息,这样即使有人截取了消息没有密码也无法知道消息的内容。由此便实现了机密性。
基于DES实现加密和解密
package com.cy.utils; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; /** * DES加密介绍 DES是一种对称加密算法,所谓对称加密算法即:加密和解密使用相同密钥的算法。DES加密算法出自IBM的研究, * 后来被美国政府正式采用,之后开始广泛流传,但是近些年使用越来越少,因为DES使用56位密钥,以现代计算能力, * 24小时内即可被破解。虽然如此,在某些简单应用中,我们还是可以使用DES加密算法,本文简单讲解DES的JAVA实现 。 * 注意:DES加密和解密过程中,密钥长度都必须是8的倍数 */ public class DESUtils { public DESUtils(){
}
/** * 加密 */ public static void encrypt(byte[] datasource, String password){ try { SecureRandom secureRandom = new SecureRandom(); DESKeySpec desKeySpec = new DESKeySpec(password.getBytes()); // 创建一个密匙工厂,然后用它把DESKeySpec转换成 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec); // Cipher对象实际完成加密操作 Cipher cipher = Cipher.getInstance("DES"); // 用密匙初始化Cipher对象,ENCRYPT_MODE用于将 Cipher 初始化为加密模式的常量 cipher.init(Cipher.ENCRYPT_MODE, secretKey, secureRandom); // 现在,获取数据并加密 // 正式执行加密操作 cipher.doFinal(datasource);// 按单部分操作加密或解密数据,或者结束一个多部分操作 } catch (Exception e) { e.printStackTrace(); } }
/** * 解密 */ public static byte[] decrypt(byte[] src, String password) throws Exception{ // DES算法要求有一个可信任的随机数源 SecureRandom secureRandom = new SecureRandom(); // 创建一个DESKeySpec对象 DESKeySpec desKeySpec = new DESKeySpec(password.getBytes()); // 创建一个密匙工厂 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES"); // 将DESKeySpec对象转换成SecretKey对象 SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec); // Cipher对象实际完成解密操作 Cipher cipher = Cipher.getInstance("DES"); // 用密匙初始化Cipher对象 cipher.init(Cipher.DECRYPT_MODE, secretKey, secureRandom); // 真正开始解密操作 return cipher.doFinal(src); } } |
非对称加密
使用一对密钥:一个用于加密信息,另一个则用于解密信息。
两个密钥之间存在着相互依存关系:即用其中任一个密钥加密的信息只能用另一个密钥进行解密。
其中加密密钥不同于解密密钥,公钥加密私钥解密,反之也可私钥加密公钥解密。
密钥依据性质划分,将其中的一个向外界公开,称为公钥;另一个则自己保留,称为私钥。公钥(Public key)常用于数据加密(用对方公钥加密)或签名验证(用对方公钥解密),私钥(Private key)常用于数据解密(发送方用接收方公钥加密)或数字签名(用自己私钥加密)。
机密性、完整性、抗抵赖性
1.使用过程:
乙方生成两把密钥(公钥和私钥)
甲方获取乙方的公钥,然后用它对信息加密。
乙方得到加密后的信息,用私钥解密,乙方也可用私钥加密字符串
甲方获取乙方私钥加密数据,用公钥解密
优点:难破解
缺点: 加密速度慢
常用算法:
RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)
RSA 工具类
package com.cy.utils; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; import org.apache.commons.codec.binary.Base64; /** * RSA加解密工具类 * @author ymk * */ public class RSAUtils { public static String publicKey; //公钥 public static String privateKey; //私钥
// KeyFactory keyFactory;
/** * 生成公钥和私钥 */ public static void generateKey(){ //初始化私钥 KeyPairGenerator keyPairGenerator; try { keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom random = new SecureRandom(); //随机数生成器 keyPairGenerator.initialize(512, random); //设置512位长的秘钥 KeyPair keyPair = keyPairGenerator.generateKeyPair(); //开始创建 RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); //进行转码 publicKey = Base64.encodeBase64String(rsaPublicKey.getEncoded()); privateKey = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } }
/** * 私钥加密或者解密 */ public static String encryptByPrivateKey(String content, String privateKeyStr, int opmode){ // 私钥要用PKCS8进行处理 PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr)); KeyFactory keyFactory; PrivateKey privateKey; Cipher cipher; byte[] result; String text = null; try { keyFactory = KeyFactory.getInstance("RSA"); //还原key对象 privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); cipher = Cipher.getInstance("RSA"); cipher.init(opmode, privateKey); if (opmode == Cipher.ENCRYPT_MODE) {// 加密 result = cipher.doFinal(content.getBytes()); text =Base64.encodeBase64String(result); }else if (opmode == Cipher.DECRYPT_MODE) {//解密 result = cipher.doFinal(Base64.decodeBase64(content)); text = new String(result, "UTF-8"); } } catch (Exception e) { e.printStackTrace(); } return text; } /** * 公钥匙加密或解密 */ @SuppressWarnings("static-access") public static String encryptByPublicKey(String content, String publicKeyStr, int opmode){ // 公钥要用X509进行处理 X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr)); KeyFactory keyFactory; PublicKey publicKey; Cipher cipher; byte[] result; String text = null; try { keyFactory = KeyFactory.getInstance("RSA"); // 还原Key对象 publicKey = keyFactory.generatePublic(x509EncodedKeySpec); cipher = Cipher.getInstance("RSA"); cipher.init(opmode, publicKey); if (opmode == Cipher.ENCRYPT_MODE) {// 加密 result = cipher.doFinal(content.getBytes()); text = Base64.encodeBase64String(result); }else if (opmode == cipher.DECRYPT_MODE) { result = cipher.doFinal(Base64.decodeBase64(content)); text = new String(result, "UTF-8"); } } catch (Exception e) {
} return text; } //测试方法 public static void main(String[] args) { /** * 注意: 私钥加密必须公钥解密 公钥加密必须私钥解密 */ System.out.println("-------------生成两对秘钥,分别发送方和接收方保管-------------"); RSAUtils.generateKey(); System.out.println("公钥匙给接收方:" + RSAUtils.publicKey); System.out.println("私钥给发送方:" + RSAUtils.privateKey); System.out.println("-------------第一个例子,私钥加密公钥解密-------------"); String textsr = "早啊,你吃早饭了吗?O(∩_∩)O~"; // 私钥加密 String cipherText = RSAUtils.encryptByPrivateKey(textsr, RSAUtils.privateKey, Cipher.ENCRYPT_MODE); System.out.println("发送方用私钥加密后:" + cipherText); // 公钥解密 String text = RSAUtils.encryptByPublicKey(cipherText, RSAUtils.publicKey, Cipher.DECRYPT_MODE); System.out.println("接收方用公钥解密后:" + text);
System.out.println("-------------第二个例子,公钥加密私钥解密-------------"); // 公钥加密 String textsr2 = "吃过啦!你吃了吗?O(∩_∩)O~"; String cipherText2 = RSAUtils.encryptByPublicKey(textsr2, RSAUtils.publicKey, Cipher.ENCRYPT_MODE); System.out.println("接收方用公钥加密后:" + cipherText2); // 私钥解密 String text2 = RSAUtils.encryptByPrivateKey(cipherText2, RSAUtils.privateKey, Cipher.DECRYPT_MODE); System.out.print("发送方用私钥解密后:" + text2); } } |
基于令牌方式隐藏参数
package com.cy.controller; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.cy.base.BaseApiService; import com.cy.base.ResponseBase; import com.cy.utils.BaseRedisService; @RestController public class PayController extends BaseApiService { @Autowired private BaseRedisService baseRedisService; private static long timeToken = 15 * 60l; @RequestMapping("/pay") public ResponseBase pay(String token) { // 获取提交参数 数据库保存., if (StringUtils.isEmpty(token)) { return setResultError("token 不能为空!"); } String reuslt = (String) baseRedisService.getString(token); if (StringUtils.isEmpty(reuslt)) { return setResultError("参数不能空!"); } System.out.println("获取提交的参数reuslt:" + reuslt); return setRestultSuccess("获取提交的参数reuslt:" + reuslt); } @RequestMapping("/getPayToken") public String pay(Long userId, Long money) { String payToken = UUID.randomUUID().toString(); baseRedisService.setString(payToken, userId + "-" + money, timeToken); return payToken; } } |
以上是关于互联网API开放平台安全设计-接口安全加密传输对称加密与非对称加密的主要内容,如果未能解决你的问题,请参考以下文章