SSH 協議採用「非對稱加密交換對稱密鑰,對稱加密傳輸數據」的混合加密策略,兼顧安全性與效能。本文實戰記錄如何在 Java 中實現此機制,構建一套基於 WebService 的軟體授權/到期控制體系:客戶端請求公鑰 → 生成隨機會話鑰 → 公鑰加密傳遞 → 雙方持有會話鑰 → 後續業務數據對稱加密傳輸。
客戶端 服務端 │ │ ├─ 1. 請求公鑰 ─────────────────────────► │ │ ◄── 2. 返回公鑰(保留私鑰) ────────────┤ │ │ ├─ 3. 生成隨機會話鑰 (echoStr) │ ├─ 4. 公鑰加密 echoStr ─────────────────► │ │ ◄── 5. 私鑰解密得 echoStr,存儲會話鑰 ────┤ │ │ ├─ 6. 業務數據 + echoStr (DES 加密) ─────► │ │ ◄── 7. echoStr 解密 → 業務邏輯 → 結果 │ │ echoStr 加密回傳 ─────────────────┤ │ │
| 加密類型 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 非對稱 (RSA) | 無需預共享密鑰,安全分發 | 運算極慢,數據量受限(鑰長度限制) | 密鑰交換、數位簽名 |
| 對稱 (DES/AES) | 極快、無長度限制、適合大數據 | 需安全分發共享密鑰 | 會話數據加密 |
| 混合 | 兼具安全分發與高效傳輸 | 實現複雜度稍高 | SSH、TLS、本文授權體系 |
public class ExchangeService {
private String privateKey; // 服務端私鑰,生命週期與請求綁定(簡化演示)
public byte[] request(String param, String resultType) {
KeyResult keyResult = new KeyResult();
try {
// 步驟 1:客戶端請求公鑰
if (PUBLIC_KEY_TYPE.equals(resultType)) {
Map<String, Object> keyMap = RSACoder.initKey(); // 生成密鑰對
privateKey = RSACoder.getPrivateKey(keyMap);
keyResult.setKey(RSACoder.getPublicKey(keyMap)); // 返回公鑰
return JsonUtil.objectToByte(keyResult);
}
// 步驟 2:接收並解密會話鑰
if (ECHO_STR_TYPE.equals(resultType)) {
byte[] paramByte = new BASE64Decoder().decodeBuffer(param);
// 私鑰解密得到客戶端生成的隨機字串
this.echoStr = new String(RSACoder.decryptByPrivateKey(paramByte, privateKey));
return JsonUtil.objectToByte(keyResult); // 確認收到
}
// 步驟 3:業務處理(授權驗證)
// 解密業務參數:BASE64 解碼 → DES 解密(echoStr)
String decryptedParam = new String(CryptUtil.decrypt(
new BASE64Decoder().decodeBuffer(param), echoStr.getBytes()));
ExchangeInfo info = ExchangeInfo.dao.getInfoByName(decryptedParam);
String result = "0"; // 預設不可用
if (PRIVILEGE_TYPE.equals(resultType)) {
long daysUsed = (System.currentTimeMillis()/1000 - string2DateInt(info.getOpenDay())) / 86400;
if (info.getUseDays() - daysUsed > 0) result = "1"; // 授權有效
}
// 結果用會話鑰加密回傳
keyResult.setResult(CryptUtil.encrypt(result.getBytes(), echoStr.getBytes()));
return JsonUtil.objectToByte(keyResult);
} catch (Exception e) {
log.error("WebService 處理異常", e);
return null;
}
}
}
public class ExchangeUtil {
public static boolean canRunForExchange(String resultType) {
int retry = 0;
while (retry++ < 10) { // 重試機制應對網路抖動
try {
ExchangeServiceProxy proxy = new ExchangeServiceProxy();
BASE64Encoder encoder = new BASE64Encoder();
// 1. 獲取公鑰
KeyResult kr = JsonUtil.byteToObject(
proxy.request(null, PUBLIC_KEY_TYPE), KeyResult.class);
// 2. 生成 10 位隨機會話鑰,公鑰加密發送
String echoStr = StrUtil.getEchoStrByLength(10);
byte[] encryptedEcho = RSACoder.encryptByPublicKey(echoStr.getBytes(), kr.getKey());
proxy.request(encoder.encode(encryptedEcho), ECHO_STR_TYPE);
// 3. 業務請求:參數 DES 加密
byte[] encryptedParam = CryptUtil.encrypt(Constants.CLIENT_TYPE.getBytes(), echoStr.getBytes());
byte[] respBytes = proxy.request(encoder.encode(encryptedParam), resultType);
// 4. 解密結果
KeyResult resp = JsonUtil.byteToObject(respBytes, KeyResult.class);
String decrypted = new String(CryptUtil.decrypt(resp.getResult(), echoStr.getBytes()));
return "1".equals(decrypted);
} catch (Exception e) {
log.warn("第 {} 次授權請求失敗: {}", retry, e.getMessage());
Thread.sleep(1000); // 指數退避可優化
}
}
return false; // 多次失敗視為授權失效
}
}
public class CryptUtil {
private static final String ALGO = "DES/CBC/PKCS5Padding";
// 密鑰截取/補齊為 8 字節
private static byte[] fixKey(byte[] key) {
byte[] k = new byte[8];
System.arraycopy(key, 0, k, 0, Math.min(key.length, 8));
return k;
}
public static byte[] encrypt(byte[] data, byte[] key) throws Exception {
key = fixKey(key);
Cipher cipher = Cipher.getInstance(ALGO);
DESKeySpec spec = new DESKeySpec(key);
SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = factory.generateSecret(spec);
IvParameterSpec iv = new IvParameterSpec(key); // IV = Key (簡化,生產應隨機)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
return cipher.doFinal(data);
}
public static byte[] decrypt(byte[] data, byte[] key) throws Exception {
key = fixKey(key);
Cipher cipher = Cipher.getInstance(ALGO);
DESKeySpec spec = new DESKeySpec(key);
SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = factory.generateSecret(spec);
IvParameterSpec iv = new IvParameterSpec(key);
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return cipher.doFinal(data);
}
}
public class RSACoder {
public static final String KEY_ALGORITHM = "RSA";
public static final String PUBLIC_KEY = "RSAPublicKey";
public static final String PRIVATE_KEY = "RSAPrivateKey";
// 初始化密鑰對 (預設 1024 位,建議 2048+)
public static Map<String, Object> initKey() throws Exception {
KeyPairGenerator gen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
gen.initialize(1024); // 生產環境建議 2048 或 4096
KeyPair pair = gen.generateKeyPair();
Map<String, Object> map = new HashMap<>();
map.put(PUBLIC_KEY, pair.getPublic());
map.put(PRIVATE_KEY, pair.getPrivate());
return map;
}
// 公鑰加密 / 私鑰解密 (BASE64 編碼傳輸)
public static byte[] encryptByPublicKey(byte[] data, String publicKey) { ... }
public static byte[] decryptByPrivateKey(byte[] data, String privateKey) { ... }
// 獲取編碼後的鑰字串
public static String getPublicKey(Map<String, Object> keyMap) { ... }
public static String getPrivateKey(Map<String, Object> keyMap) { ... }
}
SecureRandom;每次會話生新鑰,避免重放攻擊。本文完整演示了 Java 實現 SSH 風格混合加密授權體系:RSA 安全交換 DES 會話鑰,後續業務對稱加密輸。代碼結構清晰分層,涵蓋密鑰生成、交換、加解密、業務邏輯、重試容錯。生產環境務必升級至 AES-GCM + RSA-OAEP,並完善密鑰全生命週期管理,才能構建真正可信的軟體保護機制。