当前位置: 首页 > news >正文

SM4 ECB跨语言加解密:JavaScript与Java互通实现详解

1. 项目概述:为什么需要前后端统一的SM4 ECB加密?

最近在做一个涉及敏感数据传输的项目,前端是JavaScript,后端是Java。安全审计明确要求使用国密算法SM4,并且为了简化流程,初步选定ECB模式。这听起来是个标准需求,对吧?但真动起手来,我发现坑还真不少。网上搜到的代码片段要么是纯前端的,要么是纯后端的,要么就是加密解密结果对不上,让人头疼。

SM4作为国家密码管理局认定的商用密码算法,其安全性和国产化属性在金融、政务等领域应用越来越广。ECB(电子密码本)模式虽然因为其“相同明文块产生相同密文块”的特性,在安全性上不如CBC等模式,常用于加密密钥等短数据,但其实现简单、无需初始化向量(IV)、支持并行计算的特点,使其在特定场景下仍有不可替代的价值。比如,对一些已经经过安全封装、长度固定的令牌(Token)或关键标识进行二次加密。

这个项目的核心挑战,不在于理解SM4或ECB的原理,而在于实现跨语言(JS和Java)的加密解密结果完全一致。这涉及到编码、字节处理、填充方式等一系列细节的严格对齐。今天,我就把趟过的坑和最终验证通过的完整方案分享出来,包括可运行的代码和必须注意的“魔鬼细节”。

2. 核心思路与方案选型:对齐是唯一准则

要实现JS和Java的加解密互通,核心就两个字:对齐。任何微小的不一致都会导致失败。我们的方案必须确保以下五个方面在两端完全一致:

2.1 算法与模式对齐

这很明确:SM4算法,ECB模式。ECB模式不需要初始化向量(IV),这减少了一个需要对齐的变量,但也意味着我们必须更关注填充(Padding)方式。

2.2 密钥处理对齐

SM4的密钥是128位(16字节)。无论前端后端,我们接收的密钥通常是一个字符串(比如32位的十六进制字符串或一个密码文本)。我们必须确保将这个字符串转换成完全相同的16字节数组。

  • 关键决策:密钥来源。为了最大程度减少干扰,我们约定:加解密使用的密钥是一个32位的十六进制字符串(例如:0123456789abcdeffedcba9876543210)。这样,它本身就明确代表了16字节的二进制数据,避免了从文本密码通过摘要算法(如MD5、SHA-256)派生密钥时可能产生的额外不一致。

2.3 数据编码与填充对齐

这是最容易出错的地方。

  1. 明文/密文格式:我们要求输入和输出都是十六进制(Hex)字符串。这便于网络传输、日志记录和调试。在内存中,加解密操作的对象是二进制字节数组。
  2. 填充(Padding)方式:SM4是分组密码,块大小是128位(16字节)。当明文长度不是16字节的整数倍时,必须进行填充。PKCS#7/PKCS#5填充是标准且互通性最好的选择。它的规则是:缺N个字节,就填充N个值为N的字节。例如,一个15字节的数据,填充1个0x01;一个14字节的数据,填充2个0x02,以此类推。Java的Cipher类默认使用PKCS5Padding(在16字节块大小下等同于PKCS#7),而我们需要在前端JS中实现完全相同的逻辑。

2.4 代码库选型

  • Java端:优先使用Bouncy Castle(BC)密码库。它是Java领域功能最全的密码学提供者,对国密算法支持良好。JDK标准库本身不包含SM4。
  • JavaScript端:选择sm-crypto库。这是一个专门为国密算法(SM2, SM3, SM4)实现的、经过验证的JavaScript库,API清晰,支持ECB和CBC模式。

2.5 工作流程设计

整个加解密流程可以抽象为以下步骤,必须确保JS和Java的每一步都镜像一致:

  1. 输入:接收十六进制字符串格式的密钥和明文(加密时)/密文(解密时)。
  2. 解码:将十六进制字符串转换为二进制字节数组(Uint8Arraybyte[])。
  3. 配置密码器:初始化SM4密码器,设定为ECB模式、PKCS#7填充。
  4. 执行操作:进行加密或解密,得到二进制结果字节数组。
  5. 编码输出:将结果字节数组编码回十六进制字符串。

3. 核心细节解析与实操要点

3.1 十六进制字符串与字节数组的转换

这是所有数据交互的基础,必须零误差。

JavaScript实现要点:sm-crypto库的sm4.encryptsm4.decrypt方法默认接受并返回十六进制字符串。这省去了我们手动转换的麻烦。但我们需要理解其内部过程:当你传入一个十六进制明文字符串,库会先将其解析为字节数组,加密后再将字节数组编码为十六进制字符串输出。解密过程反之。

一个常见的坑:如果你的明文不是纯十六进制字符串,而是普通文本(如“HelloWorld”),你需要先将其转换为UTF-8编码的字节数组,再转换为十六进制字符串,或者直接寻找库是否支持文本输入。为了简化,我们全程使用十六进制格式。

Java实现要点:Java中需要手动处理转换。我们可以使用Hex类(来自BC库或Apache Commons Codec)来完成。

import org.bouncycastle.util.encoders.Hex; // 十六进制字符串 -> 字节数组 String hexStr = "0123456789abcdef"; byte[] bytes = Hex.decode(hexStr); // 使用BC的Hex解码 // 字节数组 -> 十六进制字符串 byte[] resultBytes = ...; String resultHex = Hex.toHexString(resultBytes); // 使用BC的Hex编码

注意:确保编解码时字母大小写一致。通常统一使用小写。Hex.toHexString默认输出小写。

3.2 PKCS#7填充的手动实现(理解原理)

虽然库会处理填充,但理解其原理对调试至关重要。假设我们要加密的数据字节数组是dataBytes,长度是dataLen

  1. 计算需要填充的字节数padLen = 16 - (dataLen % 16)。如果dataLen % 16 == 0,则padLen = 16(填充一整块)。
  2. 创建一个新的字节数组paddedData,长度为dataLen + padLen
  3. 将原数据dataBytes拷贝到paddedData的前dataLen位。
  4. paddedData[dataLen]开始,到数组结束,全部填充为(byte)padLen的值。

例如,数据[0x01, 0x02, 0x03](3字节),padLen = 13,填充后的数组为[0x01, 0x02, 0x03, 0x0d, 0x0d, 0x0d, ... (共13个0x0d)]

解密后,需要去除填充:读取最后一个字节的值padLen,然后验证最后padLen个字节的值是否都等于padLen,如果验证通过,则截取前总长度 - padLen个字节即为原始明文。

3.3 ECB模式的特点与安全提醒

务必再次强调:ECB模式不适合加密有重复模式的长文本数据。因为它缺乏扩散性,相同的明文块会产生相同的密文块。攻击者可能通过分析密文块的模式来推断部分明文信息。

适用场景:

  • 加密随机生成的、长度固定的密钥或令牌。
  • 加密已经由其他模式(如CBC)加密过的数据(即加密模式嵌套)。
  • 对安全性要求不高,但需要极高性能或简化性的内部场景。

如果你的数据不是短且随机的,强烈建议使用CBC模式并安全管理IV。本项目聚焦ECB的互通实现,但选择需谨慎。

4. 完整代码实现与逐行解析

下面提供经过验证可互通的JavaScript和Java代码。

4.1 JavaScript前端实现(基于sm-crypto)

首先,安装sm-crypto库:

npm install sm-crypto --save # 或直接在HTML中引入 <script src="https://unpkg.com/sm-crypto@latest/dist/sm-crypto.min.js"></script>

实现加密解密函数:

// 导入sm-crypto库,如果是在Node环境或使用了模块打包 const sm4 = require('sm-crypto').sm4; // 如果在浏览器直接引入script标签,则sm4是全局变量 /** * 使用SM4 ECB模式加密 * @param {string} plainHex - 十六进制字符串格式的明文 * @param {string} keyHex - 32位十六进制字符串格式的密钥 * @returns {string} 十六进制字符串格式的密文 */ function sm4EncryptECB(plainHex, keyHex) { // sm-crypto的encrypt方法默认:输入输出都是hex,模式是ECB,填充是PKCS#7 // 第一个参数:明文数据(Hex/String) // 第二个参数:密钥(Hex) // 第三个参数:模式('ecb'或'cbc') // 返回值:密文(Hex String) try { const cipherHex = sm4.encrypt(plainHex, keyHex, {mode: 'ecb'}); return cipherHex; } catch (error) { console.error('SM4加密失败:', error); throw new Error(`加密失败: ${error.message}`); } } /** * 使用SM4 ECB模式解密 * @param {string} cipherHex - 十六进制字符串格式的密文 * @param {string} keyHex - 32位十六进制字符串格式的密钥 * @returns {string} 十六进制字符串格式的明文 */ function sm4DecryptECB(cipherHex, keyHex) { // sm-crypto的decrypt方法参数与encrypt一致 try { const plainHex = sm4.decrypt(cipherHex, keyHex, {mode: 'ecb'}); return plainHex; } catch (error) { console.error('SM4解密失败:', error); throw new Error(`解密失败: ${error.message}`); } } // ============= 测试用例 ============= const testKeyHex = '0123456789abcdeffedcba9876543210'; // 128位密钥 const testPlainHex = '00112233445566778899aabbccddeeff'; // 刚好16字节的明文 console.log('密钥(Hex):', testKeyHex); console.log('明文(Hex):', testPlainHex); const encrypted = sm4EncryptECB(testPlainHex, testKeyHex); console.log('加密结果(Hex):', encrypted); const decrypted = sm4DecryptECB(encrypted, testKeyHex); console.log('解密结果(Hex):', decrypted); console.log('解密是否等于明文?', decrypted === testPlainHex);

代码解析与注意点:

  1. sm-crypto的API非常简洁,encrypt/decrypt默认处理十六进制字符串,并支持通过选项{mode: 'ecb'}指定模式。
  2. 密钥keyHex必须是32个字符的十六进制字符串(0-9, a-f),代表16字节。如果长度不对,库会抛出错误。
  3. 明文plainHex的长度必须是16字节的整数倍吗?不是。库会自动处理PKCS#7填充。你可以传入任意长度的十六进制字符串(偶数个字符,因为每两个字符代表一字节)。
  4. 错误处理很重要。加密解密过程可能因为数据格式错误、密钥错误等原因失败,需要用try-catch包裹。

4.2 Java后端实现(基于Bouncy Castle)

首先,在项目中添加Bouncy Castle依赖。以Maven为例:

<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15to18</artifactId> <version>1.74</version> <!-- 请使用最新版本 --> </dependency>

实现工具类:

import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Hex; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.security.Security; public class Sm4EcbUtils { static { // 静态代码块,确保Bouncy Castle提供者被注册 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } // 算法名称:SM4/ECB/PKCS5Padding // 在16字节块大小下,PKCS5Padding等同于PKCS7Padding private static final String ALGORITHM_NAME = "SM4"; private static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding"; /** * SM4 ECB模式加密 * @param plainHex 十六进制字符串格式的明文 * @param keyHex 32位十六进制字符串格式的密钥 * @return 十六进制字符串格式的密文 */ public static String encrypt(String plainHex, String keyHex) throws Exception { // 1. 将十六进制密钥字符串转换为字节数组 byte[] keyBytes = Hex.decode(keyHex); if (keyBytes.length != 16) { throw new IllegalArgumentException("密钥长度必须为16字节(128位),请提供32位Hex字符串"); } // 2. 将十六进制明文字符串转换为字节数组 byte[] plainBytes = Hex.decode(plainHex); // 3. 创建密钥规范 SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM_NAME); // 4. 获取并初始化Cipher实例 // 使用Bouncy Castle的提供者 Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); // 5. 执行加密 byte[] cipherBytes = cipher.doFinal(plainBytes); // 6. 将密文字节数组转换为十六进制字符串 return Hex.toHexString(cipherBytes); } /** * SM4 ECB模式解密 * @param cipherHex 十六进制字符串格式的密文 * @param keyHex 32位十六进制字符串格式的密钥 * @return 十六进制字符串格式的明文 */ public static String decrypt(String cipherHex, String keyHex) throws Exception { // 1. 将十六进制密钥字符串转换为字节数组 byte[] keyBytes = Hex.decode(keyHex); if (keyBytes.length != 16) { throw new IllegalArgumentException("密钥长度必须为16字节(128位),请提供32位Hex字符串"); } // 2. 将十六进制密文字符串转换为字节数组 byte[] cipherBytes = Hex.decode(cipherHex); // 3. 创建密钥规范 SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, ALGORITHM_NAME); // 4. 获取并初始化Cipher实例 Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); // 5. 执行解密 byte[] plainBytes = cipher.doFinal(cipherBytes); // 6. 将明文字节数组转换为十六进制字符串 return Hex.toHexString(plainBytes); } public static void main(String[] args) { try { String testKeyHex = "0123456789abcdeffedcba9876543210"; String testPlainHex = "00112233445566778899aabbccddeeff"; System.out.println("密钥(Hex): " + testKeyHex); System.out.println("明文(Hex): " + testPlainHex); String encrypted = encrypt(testPlainHex, testKeyHex); System.out.println("加密结果(Hex): " + encrypted); String decrypted = decrypt(encrypted, testKeyHex); System.out.println("解密结果(Hex): " + decrypted); System.out.println("解密是否等于明文? " + testPlainHex.equals(decrypted)); } catch (Exception e) { e.printStackTrace(); } } }

代码解析与注意点:

  1. 提供者注册:必须在操作前通过Security.addProvider注册Bouncy Castle提供者,或者在Cipher.getInstance时显式指定提供者名称(如代码所示)。后者更明确。
  2. 算法字符串"SM4/ECB/PKCS5Padding"是BC库识别的标准名称。它指明了算法、模式和填充。
  3. 密钥检查:我们主动检查了密钥字节数组的长度是否为16,这是一个好习惯,可以提前发现参数错误。
  4. 异常处理doFinal方法可能抛出BadPaddingException等异常,在正式环境中应妥善处理,例如记录日志并返回统一的错误响应。

4.3 互通性验证测试

分别运行上述JS和Java的测试代码。你会发现,使用相同的密钥和明文,两者加密产生的密文完全一致,并且都能正确解密对方产生的密文。这是项目成功的标志。

你可以尝试更多测试用例:

  1. 短数据plainHex = "616263"(对应文本"abc"的UTF-8编码的Hex)。
  2. 非16倍数长数据plainHex = "00112233445566778899aabbccddee"(15字节)。
  3. 长文本:先将一段UTF-8文本(如"Hello, 世界!")通过Buffer.from(str, 'utf8').toString('hex')(Node.js)或new TextEncoder().encode(str)转Hex(浏览器)得到plainHex,再进行加解密。

5. 常见问题、排查技巧与实战心得

即使代码看起来一样,在实际集成中你可能还是会遇到问题。下面是我踩过的一些坑和解决方法。

5.1 密文对不上?逐层排查法

当JS和Java加密结果不一致时,不要慌,按照以下步骤隔离问题:

  1. 检查输入一致性:这是最常被忽略的。确保两端传入的keyHexplainHex字符串完全一样,包括大小写。最好在函数入口打印或日志记录这两个值的长度和内容。一个空格、一个换行符的差异都会导致结果不同。
  2. 验证密钥和明文字节:在两端分别将Hex字符串解码成字节数组,并打印数组长度和内容。确保字节级一致。
    • Java:System.out.println(Arrays.toString(Hex.decode(keyHex)));
    • JavaScript:console.log(Array.from(sm4.utils.hexToBytes(keyHex)));sm-cryptoutils里有转换工具)
  3. 检查算法参数:确认两端都是SM4ECB模式、PKCS#7填充。Java的PKCS5Padding在这里是等价的。
  4. 单独测试填充:构造一个非16倍数的明文,分别在两端手动实现PKCS#7填充,比较填充后的字节数组是否一致。这能排除填充逻辑的差异。
  5. 使用标准测试向量:在网上搜索SM4 ECB的官方或社区测试向量(一组确定的密钥、明文和密文)。用它们分别测试你的JS和Java代码,看是否能通过。这能直接判断你的核心加解密逻辑是否正确。

5.2 Java端报错:No such provider: BC 或 NoSuchAlgorithmException

  • 原因:Bouncy Castle提供者未正确注册或加载。
  • 解决
    • 确保bcprov-jdk15to18.jar在项目的classpath中。
    • 确认静态代码块或初始化代码成功执行了Security.addProvider(new BouncyCastleProvider())
    • 尝试使用显式提供者的方式获取Cipher实例(如我们的代码所示):Cipher.getInstance("SM4/ECB/PKCS5Padding", "BC")Cipher.getInstance("SM4/ECB/PKCS5Padding", BouncyCastleProvider.PROVIDER_NAME)

5.3 解密时报错:Given final block not properly padded

  • 原因:密文损坏、密钥错误、或者加密解密使用的模式/填充不一致。
  • 排查
    1. 检查传输过程中密文Hex字符串是否被截断或修改。网络传输时,确保使用纯文本模式或正确编码。
    2. 确认解密使用的密钥与加密时完全一致。
    3. 最关键的一点:确认Java和JS两端使用的是相同的填充模式。如果一端是PKCS5Padding,另一端是NoPadding(无填充),但数据长度又不是16的倍数,解密时必然失败。

5.4 性能与优化建议

  • 密钥和Cipher对象复用:在Java服务端,如果频繁进行加解密操作,不要每次调用都Cipher.getInstance()init()。可以考虑使用ThreadLocal缓存初始化好的Cipher对象,但要注意线程安全。对于固定密钥的场景,可以缓存SecretKeySpec
  • JS端数据量:在浏览器中进行大量数据加密可能会阻塞主线程。对于大数据量,考虑使用Web Worker在后台线程处理。
  • Hex编码开销:Hex编码会使数据体积膨胀一倍(1字节变2字符)。如果传输效率是瓶颈,且通信双方都支持,可以考虑使用Base64编码(体积增加约33%)。但需要同时修改JS和Java的编解码部分。sm-crypto也支持Base64输入输出。

5.5 安全强化实践

虽然本项目是ECB模式,但安全原则不容忽视:

  • 密钥管理:绝对不要将密钥硬编码在前端代码中。前端加密使用的密钥应由后端在会话开始时动态下发(通过安全通道如HTTPS),并且可以定期更换。更安全的做法是,前端只用于展示,所有加解密操作由后端API完成。
  • 升级到CBC或GCM模式:对于真实敏感数据,尽快评估并迁移到CBC(需要安全生成和管理IV)或GCM(同时提供加密和认证)模式。sm-crypto和Bouncy Castle都支持这些模式,但互通实现需要额外对齐IV或Nonce。
  • 完整性校验:ECB和CBC模式只提供机密性,不提供完整性。考虑结合HMAC-SM3等算法对密文进行签名,防止密文被篡改。

6. 从ECB扩展到其他模式与高级话题

当你掌握了ECB模式的互通之后,解决其他模式的问题就有了坚实的基础。核心思路不变:对齐所有参数

6.1 实现SM4 CBC模式互通

CBC模式需要增加一个初始化向量(IV),它也是一个16字节(128位)的数据块。实现互通的关键点:

  1. IV的生成与传递:IV不需要保密,但必须不可预测。通常由加密方随机生成,并随密文一起传递给解密方。IV也需要以十六进制字符串格式对齐。
  2. 代码改动
    • JavaScript (sm-crypto)encryptdecrypt方法需要增加iv参数。{mode: 'cbc', iv: ivHex}
    • Java (Bouncy Castle):算法字符串改为"SM4/CBC/PKCS5Padding"。初始化Cipher时,需要使用IvParameterSpeccipher.init(mode, secretKeySpec, new IvParameterSpec(ivBytes))
  3. 安全提醒:同一个密钥下,绝对不要重复使用相同的IV。

6.2 处理非Hex格式的输入(文本、Base64)

实际业务中,数据可能不是Hex格式。你需要一个统一的预处理层。

场景:加密普通文本字符串

  1. 约定编码:两端统一使用UTF-8编码将字符串转换为字节数组。
  2. 转换步骤
    • 加密前:文本 -> UTF-8字节数组 -> Hex字符串 -> 调用上述加密函数。
    • 解密后:获取Hex明文 -> Hex字符串转字节数组 -> UTF-8解码 -> 文本。
  3. 工具函数示例(JavaScript)
    function strToHex(str) { return Array.from(new TextEncoder().encode(str)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } function hexToStr(hex) { const bytes = new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); return new TextDecoder().decode(bytes); } // 使用 const textPlain = '你好,Hello123!'; const plainHexForEncrypt = strToHex(textPlain); const encryptedHex = sm4EncryptECB(plainHexForEncrypt, keyHex); // ... 传输encryptedHex ... // 解密后 const decryptedHex = sm4DecryptECB(encryptedHex, keyHex); const finalText = hexToStr(decryptedHex);
  4. Java端对应实现:使用String.getBytes(StandardCharsets.UTF_8)new String(bytes, StandardCharsets.UTF_8)进行转换。

6.3 在线调试与验证工具

在开发过程中,可以使用一些在线工具进行交叉验证,但切勿用于生产环境真实密钥和数据的测试!

  • 寻找在线的SM4加密解密工具。
  • 用你的JS代码加密一段数据,然后用在线工具(如果支持SM4 ECB)使用相同密钥解密,看结果是否正确。这可以帮助你快速定位问题是出在加密端还是解密端。

最后,封装一个好的加解密工具类或模块,对外提供清晰的接口(如encryptText(keyHex, text)decryptToText(keyHex, cipherHex)),并做好错误处理和日志记录,这将大大提升代码的可用性和可维护性。记住,密码学实现无小事,细节决定成败,充分的测试是保证稳定互通的唯一途径。

http://www.gsyq.cn/news/1645976.html

相关文章:

  • Pytest+Requests接口自动化框架实战:解决接口关联与工程化难题
  • Sigmoid函数的工程价值:概率建模、模型校准与边缘部署
  • Python XML解析漏洞实战排查:Ruff安全扫描10大技巧
  • 基于RPA-Python、pytest与Spark构建企业级分布式测试自动化框架
  • 拉曼光谱定量分析:PLS与PCR 2种数学模型构建对比与Python实现
  • 手游漏洞挖掘入门:从数据包分析到逻辑漏洞实战
  • 阿里Rand逆向分析:从原理到实战的完整指南
  • C#超市会员系统实战包:WinForm界面+SQL Server数据库+完整课设文档
  • MD5哈希算法深度解析:从原理到代码实现与安全实践
  • WebShell查杀实战:从原理到工具选型与应急响应全流程
  • 电商系统测试全攻略:从功能到高并发,面试与实战核心要点解析
  • CTF实战:FTP服务后门漏洞利用与渗透测试全流程解析
  • 逆向工程实战:深度解析iTunes登录协议与安全机制
  • AI实战指南:从工具选型到RAG应用开发全流程解析
  • 加密系统攻防实战:从算法漏洞到密钥管理的全方位防御指南
  • 单例模式双重检查锁与静态内部类实现对比
  • 文件上传漏洞实战:从BurpSuite抓包到Webshell上传的攻防复盘
  • 逻辑漏洞挖掘实战:从业务流分析到十大高频场景攻防
  • Wireshark实战:从SMTP流量中快速定位与分析钓鱼邮件
  • XGBoost 2.0.3 参数调优实战:10个关键参数对鸢尾花分类准确率的影响
  • Web安全实战:任意URL跳转漏洞原理、挖掘与防御全解析
  • 英雄联盟Seraphine助手:终极智能BP与战绩查询工具完全指南
  • PyTorch版Deeplabv3+语义分割代码包,含ASPP模块与多骨干网络支持
  • 利用冷门语言绕过Windows Defender实现零检出反弹Shell
  • SSH密钥管理完全指南:从生成到轮换的安全实践
  • 真理的建构性与相对性
  • Java反混淆实战:Deobfuscator工具原理与逆向工程应用
  • SpringBoot配置加密实战:Jasypt 3.0.2集成与Docker安全部署指南
  • ESP32-S3用VSCode直接读SD卡BMP图并刷到ST7789类LCD屏(纯C/ESP-IDF工程)
  • 从希尔密码到现代加密:矩阵加密原理、实现与攻击实战