Needham-Schroeder協議原理及實現(Java)

基本介紹

Needham-Schroeder是一個基於對稱加密算法的協議,它要求有可信任的第三方KDC參與,採用Challenge/Response的方式,使得A、B互相認證對方的身份。

協議過程

(1)AKDC:IDAIDBN1(1)A→KDC:ID_A||ID_B||N_1

(2)KDCAEKa[KSIDBN1,Ekb[KSIDA]](2)KDC→A:E_{K_a}[K_S||ID_B||N_1,E_{k_b}[K_S||ID_A]]

(3)AB:EKb[KSIDA](3)A→B:E_{K_b}[K_S||ID_A]

(4)BA:EKS[N2](4)B→A:E_{K_S}[N_2]

(5)AB:EKS[f(N2)](5)A→B:E_{K_S}[f(N_2)]

  • KDC是密鑰分發中心
  • ID表示A的身份標識。
  • 密鑰Ka和Kb分別是A、B和KDC的共享的密鑰。
  • N1和N2是兩個隨機數

白話描述

前提:A、B用戶均已與KDC共享其公鑰,即在KDC中存儲有A/B的對稱密鑰

  1. A用戶創建一個隨機數NN發送到KDC
  2. KDC產生一個隨機公鑰KK,利用B的密鑰對{K,IDA}\{K,ID_A\}進行加密,並利用A的密鑰對{N,K,IDB,{K,IDA}KBT}\{N,K,ID_B,\{K,ID_A\}_{K_BT}\}({}KBT\{\}{K_BT}即表示使用B密鑰進行對{}\{\}中的內容加密)
  3. A用戶收到KDC的返回信息對其解密後,對NNIDBID_B進行覈對,檢查是否正確。覈對完成後將{K,IDA}KBT}\{K,ID_A\}_{K_BT}\}這部分轉發給B
  4. B收到A發送的數據後對其進行解密,獲取到其中的KK,以及覈對IDAID_A是否正確。
  5. 到此AB雙方均獲取了一個臨時公鑰KK,可利用其對雙方之間的通訊進行加密。

協議漏洞

  1. 假定攻擊方C已經掌握A和B之間通信的一個老的會話密鑰;
  2. C冒充A,利用老的會話密鑰欺騙B。除非B記住所有以前使用的與A通信的會話密鑰,否則B無法判斷這是一個重放攻擊。
  3. 然後,C再中途阻止第3步中新的數據轉發的。從這一點起C就可以向B發送僞造的消息,而讓B認爲是在與A進行正常通信。

解決辦法

{K,IDA}KBT}\{K,ID_A\}_{K_BT}\}這塊加密數據中加入時間戳,如:{K,IDA.TimeStamp}KBT}\{K,ID_A.TimeStamp\}_{K_BT}\},以保證數據的時效性即可。即在第4步收到A轉發的數據塊後解密出數據之後,對其中的TimeStampTimeStamp進行檢測,判斷這個共享祕鑰是否已經過期。

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();
        }
    }
}

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