互聯網API開放平臺安全設計(四)--信息加密與密鑰管理

 

單向散列加密

散列是信息的提煉,通常其長度要比信息小得多,且爲一個固定長度。加密性強的散列一定是不可逆的,這就意味着通過散列結果,無法推出任何部分的原始信息。任何輸入信息的變化,哪怕僅一位,都將導致散列結果的明顯變化,這稱之爲雪崩效應。散列還應該是防衝突的,即找不出具有相同散列結果的兩條信息。具有這些特性的散列結果就可以用於驗證信息是否被修改。

單向散列函數一般用於產生消息摘要,密鑰加密等,常見的有:


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解密與加密

http://www.cmd5.com/

MD5加鹽實現方式

一般使用的加鹽:

md5(Password+UserName),即將用戶名和密碼字符串相加再MD5,這樣的MD5摘要基本上不可反查。

但有時候用戶名可能會發生變化,發生變化後密碼即不可用了(驗證密碼實際上就是再次計算摘要的過程)。

因此我們做了一個非常簡單的加鹽算法,每次保存密碼到數據庫時,都生成一個隨機16位數字,將這16位數字和密碼相加再求MD5摘要,然後在摘要中再將這16位數字按規則摻入形成一個48位的字符串。

在驗證密碼時再從48位字符串中按規則提取16位數字,和用戶輸入的密碼相加再MD5。按照這種方法形成的結果肯定是不可直接反查的,且同一個密碼每次保存時形成的摘要也都是不同的。

信息加密技術

對稱加密

對稱密碼技術:發件人和收件人使用其共同擁有的單個密鑰 ,這種密鑰既用於加密,也用於解密,叫做機密密鑰(也稱爲對稱密鑰或會話密鑰)。能夠提供信息機密性(沒有密鑰信息不能被解密)、完整性(被改變的信息不能被解密)的服務。對稱式密碼學又稱:單鑰密碼學、祕密密鑰密碼學、會話密鑰密碼學、私鑰密碼學、共享祕鑰密碼學

常見的對稱式加密技術

 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

    如果密鑰多個用戶被共享,不能提供抗抵賴性

對稱密碼案例

 假設AliceBob是認識的,兩人爲了保證通信消息不被其它人截取,預先約定了一個密碼,用來加密在他們之間傳送的消息,這樣即使有人截取了消息沒有密碼也無法知道消息的內容。由此便實現了機密性。

/**
 * DES加密介紹 DES是一種對稱加密算法,所謂對稱加密算法即:加密和解密使用相同密鑰的算法。DES加密算法出自IBM的研究,
 * 後來被美國政府正式採用,之後開始廣泛流傳,但是近些年使用越來越少,因爲DES使用56位密鑰,以現代計算能力,
 * 24小時內即可被破解。雖然如此,在某些簡單應用中,我們還是可以使用DES加密算法,本文簡單講解DES的JAVA實現 。
 * 注意:DES加密和解密過程中,密鑰長度都必須是8的倍數
 */
public class DES {
	public DES() {
	}

	// 測試
	public static void main(String args[]) {
		// 待加密內容
		String str = "cryptology";
		// 密碼,長度要是8的倍數
		String password = "95880288";

		byte[] result = DES.encrypt(str.getBytes(), password);
		System.out.println("加密後:" + new String(result));
		// 直接將如上內容解密
		try {
			byte[] decryResult = DES.decrypt(result, password);
			System.out.println("解密後:" + new String(decryResult));
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	}

	/**
	 * 加密
	 * 
	 * @param datasource
	 *            byte[]
	 * @param password
	 *            String
	 * @return byte[]
	 */
	public static byte[] encrypt(byte[] datasource, String password) {
		try {
			SecureRandom random = new SecureRandom();
			DESKeySpec desKey = new DESKeySpec(password.getBytes());
			// 創建一個密匙工廠,然後用它把DESKeySpec轉換成
			SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
			SecretKey securekey = keyFactory.generateSecret(desKey);
			// Cipher對象實際完成加密操作
			Cipher cipher = Cipher.getInstance("DES");
			// 用密匙初始化Cipher對象,ENCRYPT_MODE用於將 Cipher 初始化爲加密模式的常量
			cipher.init(Cipher.ENCRYPT_MODE, securekey, random);
			// 現在,獲取數據並加密
			// 正式執行加密操作
			return cipher.doFinal(datasource); // 按單部分操作加密或解密數據,或者結束一個多部分操作
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 解密
	 * 
	 * @param src
	 *            byte[]
	 * @param password
	 *            String
	 * @return byte[]
	 * @throws Exception
	 */
	public static byte[] decrypt(byte[] src, String password) throws Exception {
		// DES算法要求有一個可信任的隨機數源
		SecureRandom random = new SecureRandom();
		// 創建一個DESKeySpec對象
		DESKeySpec desKey = new DESKeySpec(password.getBytes());
		// 創建一個密匙工廠
		SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");// 返回實現指定轉換的
																			// Cipher
																			// 對象
		// 將DESKeySpec對象轉換成SecretKey對象
		SecretKey securekey = keyFactory.generateSecret(desKey);
		// Cipher對象實際完成解密操作
		Cipher cipher = Cipher.getInstance("DES");
		// 用密匙初始化Cipher對象
		cipher.init(Cipher.DECRYPT_MODE, securekey, random);
		// 真正開始解密操作
		return cipher.doFinal(src);
	}
}

非對稱加密

 使用一對密鑰:一個用於加密信息,另一個則用於解密信息。兩個密鑰之間存在着相互依存關係:即用其中任一個密鑰加密的信息只能用另一個密鑰進行解密。其中加密密鑰不同於解密密鑰,公鑰加密私鑰解密,反之也可私鑰加密公鑰解密。密鑰依據性質劃分,將其中的一個向外界公開,稱爲公鑰;另一個則自己保留,稱爲私鑰。公鑰(Public key)常用於數據加密(用對方公鑰加密)或簽名驗證(用對方公鑰解密),私鑰(Private key)常用於數據解密(發送方用接收方公鑰加密)或數字簽名(用自己私鑰加密)。機密性、完整性、抗抵賴性

1.使用過程:

乙方生成兩把密鑰(公鑰和私鑰)

甲方獲取乙方的公鑰,然後用它對信息加密。

乙方得到加密後的信息,用私鑰解密,乙方也可用私鑰加密字符串

甲方獲取乙方私鑰加密數據,用公鑰解密

優點:難破解

缺點: 加密速度慢

常用算法:

RSA、Elgamal、揹包算法、Rabin、D-H、ECC(橢圓曲線加密算法)

RSA 工具類

/**
 * RSA加解密工具類
 * 
 * @author QiuFeihu
 *
 */
public class RSAUtil {

	public static String publicKey; // 公鑰
	public static String privateKey; // 私鑰

	/**
	 * 生成公鑰和私鑰
	 */
	public static void generateKey() {
		// 1.初始化祕鑰
		KeyPairGenerator keyPairGenerator;
		try {
			keyPairGenerator = KeyPairGenerator.getInstance("RSA");
			SecureRandom sr = new SecureRandom(); // 隨機數生成器
			keyPairGenerator.initialize(512, sr); // 設置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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 私鑰匙加密或解密
	 * 
	 * @param content
	 * @param privateKeyStr
	 * @return
	 */
	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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return text;
	}

	/**
	 * 公鑰匙加密或解密
	 * 
	 * @param content
	 * @param privateKeyStr
	 * @return
	 */
	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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return text;
	}

	// 測試方法
	public static void main(String[] args) {
		/**
		 * 注意: 私鑰加密必須公鑰解密 公鑰加密必須私鑰解密
		 */
		System.out.println("-------------生成兩對祕鑰,分別發送方和接收方保管-------------");
		RSAUtil.generateKey();
		System.out.println("公鑰匙給接收方:" + RSAUtil.publicKey);
		System.out.println("私鑰給發送方:" + RSAUtil.privateKey);

		System.out.println("-------------第一個栗子,私鑰加密公鑰解密-------------");
		// String textsr = "早啊,你吃早飯了嗎?O(∩_∩)O~";
		// // 私鑰加密
		// String cipherText = RSAUtil.encryptByprivateKey(textsr,
		// RSAUtil.privateKey, Cipher.ENCRYPT_MODE);
		// System.out.println("發送方用私鑰加密後:" + cipherText);
		// // 公鑰解密
		// String text = RSAUtil.encryptByPublicKey(cipherText,
		// RSAUtil.publicKey, Cipher.DECRYPT_MODE);
		// System.out.println("接收方用公鑰解密後:" + text);

		System.out.println("-------------第二個栗子,公鑰加密私鑰解密-------------");
		// 公鑰加密
		String textsr = "吃過啦!你吃了嗎?O(∩_∩)O~";

		String cipherText = RSAUtil.encryptByPublicKey(textsr, RSAUtil.publicKey, Cipher.ENCRYPT_MODE);
		System.out.println("接收方用公鑰加密後:" + cipherText);
		// 私鑰解密
		String text = RSAUtil.encryptByprivateKey(cipherText, RSAUtil.privateKey, Cipher.DECRYPT_MODE);
		System.out.print("發送方用私鑰解密後:" + text);
	}

}

基於令牌方式隱藏參數

@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 setResultSuccess("獲取提交的參數reuslt:" + reuslt);
	}

	@RequestMapping("/getPayToken")
	public String pay(Long userId, Long money) {
		String payToken = UUID.randomUUID().toString();
		baseRedisService.setString(payToken, userId + "-" + money, timeToken);
		return payToken;
	}

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章