基本介紹
Needham-Schroeder
是一個基於對稱加密算法的協議,它要求有可信任的第三方KDC
參與,採用Challenge/Response
的方式,使得A、B互相認證對方的身份。
協議過程
- KDC是密鑰分發中心
- ID表示A的身份標識。
- 密鑰Ka和Kb分別是A、B和KDC的共享的密鑰。
- N1和N2是兩個隨機數
白話描述
前提:A、B用戶均已與KDC共享其公鑰,即在KDC中存儲有A/B的對稱密鑰
A
用戶創建一個隨機數發送到KDC
。KDC
產生一個隨機公鑰,利用B
的密鑰對進行加密,並利用A的密鑰對(即表示使用B
密鑰進行對中的內容加密)A
用戶收到KDC
的返回信息對其解密後,對和進行覈對,檢查是否正確。覈對完成後將這部分轉發給B
B
收到A
發送的數據後對其進行解密,獲取到其中的,以及覈對是否正確。- 到此
A
、B
雙方均獲取了一個臨時公鑰,可利用其對雙方之間的通訊進行加密。
協議漏洞
- 假定攻擊方C已經掌握A和B之間通信的一個老的會話密鑰;
- C冒充A,利用老的會話密鑰欺騙B。除非B記住所有以前使用的與A通信的會話密鑰,否則B無法判斷這是一個重放攻擊。
- 然後,C再中途阻止
第3步
中新的數據轉發的。從這一點起C就可以向B發送僞造的消息,而讓B認爲是在與A進行正常通信。
解決辦法
在這塊加密數據中加入時間戳,如:,以保證數據的時效性即可。即在第4步
收到A轉發的數據塊後解密出數據之後,對其中的進行檢測,判斷這個共享祕鑰是否已經過期。
Java模擬Needham-Schroeder協議過程
以下模擬過程中加密協議採用的是JDK自帶的DES協議,採用的是128位的密鑰長度。
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class KDC {
private static final int KEY_LENGTH = 128;//密鑰長度
private static final ByteBuffer buffer = ByteBuffer.allocate(8);//分配直接內存供數據類型轉換
private static Map<Long, SecretKey> secretKeyMap;//公鑰註冊表
static {
secretKeyMap = new HashMap<>();
}
//註冊用戶到密鑰分發中心
public static void registerUser(Long id, SecretKey secretKey) throws Exception {
if (id == null || secretKey == null) throw new Exception("Register information can't be null");
secretKeyMap.put(id, secretKey);
}
//獲取隨機密鑰
private static SecretKey createRandomKey() {
try {
SecureRandom random = new SecureRandom();
byte[] randomKey = new byte[KEY_LENGTH];
random.nextBytes(randomKey);
System.out.println("獲取的隨機公鑰爲:" + Arrays.toString(randomKey));
DESKeySpec deskeySpec = new DESKeySpec(randomKey);
return SecretKeyFactory.getInstance("DES").generateSecret(deskeySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
//獲取隨機序列號
private static Long createRandomNum() {
SecureRandom random = new SecureRandom();
byte[] randomNum = new byte[8];
random.nextBytes(randomNum);
buffer.clear();
buffer.put(randomNum, 0, randomNum.length);
buffer.flip();
return buffer.getLong();
}
//將字節數組解析爲long型
public static Long parseFromBytes(byte[] bytes) {
if (bytes.length!=8) return null;
buffer.clear();
buffer.put(bytes, 0, bytes.length);
buffer.flip();
return buffer.getLong();
}
//將long型轉爲字節數組
public static byte[] long2Bytes(Long num) {
buffer.clear();
buffer.putLong(0, num);
//這裏使用的是直接內存,array()返回的是指針,需要對相應位置進行拷貝
return Arrays.copyOf(buffer.array(), 8);
}
//合併多個字節數組
public static byte[] byteMerger(byte[]... values) {
int length_byte = 0;
for (byte[] value : values) {
length_byte += value.length;
}
byte[] all_byte = new byte[length_byte];
int countLength = 0;
for (byte[] b : values) {
System.arraycopy(b, 0, all_byte, countLength, b.length);
countLength += b.length;
}
return all_byte;
}
/**
* @param id_A 發起請求的用戶A的註冊ID
* @param id_B 被建立連接的用戶B的註冊ID
* @param randomNum 用戶A產生的隨機數
* @return 由分發中心加密後的字節數組
* @throws Exception 參數爲空即拋出異常
*/
public byte[] talkWith(Long id_A, Long id_B, Long randomNum) throws Exception {
SecretKey secretKeyA = secretKeyMap.get(id_A);
SecretKey secretKeyB = secretKeyMap.get(id_B);
if (secretKeyA == null || secretKeyB == null) throw new Exception("The user is not registered");
try {
//使用普通的DES對稱加密
Cipher enCipher = Cipher.getInstance("DES/ECB/NoPadding");
enCipher.init(Cipher.ENCRYPT_MODE, secretKeyB);
//註冊中心創建隨機數K
Long K = createRandomNum();
//獲取B公鑰加密後的{K,ID_A}
byte[] K_IDA = enCipher.doFinal(byteMerger(long2Bytes(K), long2Bytes(id_A)));
enCipher.init(Cipher.ENCRYPT_MODE, secretKeyA);
//獲取A公鑰加密後的{N,K,ID_B,{K,ID_A}KBT}
byte[] bytes = byteMerger(long2Bytes(randomNum), long2Bytes(K), long2Bytes(id_B), K_IDA);
return enCipher.doFinal(bytes);
} catch (Exception e) {
System.out.println("建立公共密鑰失敗");
e.printStackTrace();
}
return null;
}
/*模擬協議執行過程
* 前提:A、B用戶均已與KDC共享其公鑰
* 1.A用戶創建一個隨機數N發送到KDC
* 2.KDC產生一個隨機公鑰K,利用B的密鑰對{K,A_ID}進行加密,
* 並利用A的密鑰對{N,K,B_ID,{K,A_ID}KBT}(KBT即表示使用B密鑰進行加密)
* 3.A用戶收到KDC的返回信息對其解密後,對N和B_ID進行覈對,檢查是否正確。
* 覈對完成後將{K,A_ID}KBT這部分轉發給B
* 4.B收到A發送的數據後對其進行解密,獲取到其中的K,以及覈對A_ID是否正確
* 5.到此A、B雙方均獲取了一個臨時公鑰K,可利用其對雙方之間的通訊進行加密
* */
public static void main(String[] args) {
try {
KDC kdc = new KDC();
long A = 1, B = 2;
SecretKey secretKeyA = createRandomKey();
SecretKey secretKeyB = createRandomKey();
KDC.registerUser(A, secretKeyA);
KDC.registerUser(B, secretKeyB);
//A向密匙分發中心請求與B進行通訊需要獲取公共密鑰
//A->KDC
long randomNum = KDC.createRandomNum();
//KDC->A
byte[] bytes = kdc.talkWith(A, B, randomNum);
//A對返回的信息進行解密並檢查信息是否正確
Cipher deCipher = Cipher.getInstance("DES/ECB/NoPadding");
deCipher.init(Cipher.DECRYPT_MODE, secretKeyA);
byte[] rawData = deCipher.doFinal(bytes);
long N = parseFromBytes(Arrays.copyOfRange(rawData, 0, 8));
long A_K = parseFromBytes(Arrays.copyOfRange(rawData, 8, 16));
long ID_B = parseFromBytes(Arrays.copyOfRange(rawData, 16, 24));
if (N != randomNum || ID_B != B) {
System.out.println("A收到錯誤信息");
return;
}
//B公鑰加密過的信息{K,ID_A}
byte[] K_IDA = Arrays.copyOfRange(rawData, 24, rawData.length);
//A->B,將上訴加密信息發送給B,B利用自己密鑰進行解密
deCipher.init(Cipher.DECRYPT_MODE, secretKeyB);
byte[] data = deCipher.doFinal(K_IDA);
long B_K = parseFromBytes(Arrays.copyOfRange(data, 0, 8));
long ID_A = parseFromBytes(Arrays.copyOfRange(data, 8, 16));
if ((B_K == A_K) && (ID_A == A)) {
System.out.println("A&B已成功獲取臨時對稱公鑰K");
System.out.println("臨時對稱公鑰K:\n" + Arrays.toString(long2Bytes(B_K)));
} else {
System.out.println("A&B獲取臨時對稱公鑰K失敗");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}