加密与安全_优雅存储二要素(AES-256-GCM )
文章目录
- 什么是二要素
- 如何保护二要素(姓名和身份证)
- 加密算法分类
- 场景选择
- 算法选择
- AES - ECB 模式 (不推荐)
- AES - CBC 模式 (推荐)
- GCM(Galois/Counter Mode)
- AES-256-GCM简介
- AES-256-GCM工作原理
- 安全优势
- 应用场景
- 其他模式 和 敏感数据加密建议
- Code
什么是二要素
二要素(姓名和身份证)是敏感数据,,很多网站仅仅依靠二要素来确认你是谁,若以明文形式存储在数据库中,存在被攻破的风险。若这些信息被不法分子获取, 后果严重。
如何保护二要素(姓名和身份证)
单向散列算法,如MD5、SHA-256等,虽然可以对数据生成唯一的指纹,但由于其不可逆,无法用于加密需要解密的数据。因此,它不适合用于对二要素信息(如姓名和身份证)进行加密保存。
在此情况下,需要选择真正的加密算法来实现数据的加密存储与解密。
加密算法分类
-
对称加密算法:
- 对称加密算法依赖于一个相同的密钥,既用于加密,也用于解密。常见的对称加密算法包括AES、DES、3DES等。
- 在通信场景中,加密方和解密方必须事先共享密钥,双方才能进行加密和解密。密钥的共享过程是该加密方式的关键挑战之一,因为如果密钥在传输过程中被窃取或泄露,加密数据就容易被解密,安全性将大打折扣。
- 优点:对称加密的主要优势在于加密和解密的速度非常快,特别适合需要高效处理大量数据的场景。
- 缺点:密钥分发的安全性是对称加密的主要隐患。如果在通信中,密钥传输不当导致泄露,攻击者可以利用该密钥轻松解密数据。
-
非对称加密算法:
- 非对称加密算法由一对密钥构成,分别为公钥(加密密钥)和私钥(解密密钥)。常见的非对称加密算法有RSA、DSA等。
- 公钥可以任意公开,而私钥必须保持私密。使用公钥加密的数据只能由对应的私钥解密。因此,通信双方只需共享公钥即可,无需直接传输私钥,从而避免了密钥泄露的问题。
- 优点:非对称加密解决了密钥分发的安全性问题,适合用在双方未建立密钥共享机制的场景。
- 缺点:加密和解密的速度相对较慢,特别是在处理大数据量时性能不足。
场景选择
在需要加密保存二要素信息的场景下,加密和解密都是由同一个服务端程序执行,双方并不需要通过网络传输密钥,因此密钥分发的安全性问题不是关键点。相对而言,对称加密算法具有速度快、效率高的优势,更适合在服务端加密存储二要素数据。
因此,尽管非对称加密在解决密钥传输安全性上有独特优势,但在保存敏感数据的场景中,采用对称加密算法(如AES-CBC或AES-CTR模式)更为合适。
算法选择
对称加密常用算法有 DES、3DES 和 AES。
- DES 已被证明不安全,破解时间很短,不推荐使用。
- 3DES 通过三次 DES 串联调用解决了 DES 的安全性问题,但速度较慢,也不推荐使用。
- AES 是目前公认安全且高效的算法,采用 Rijndael 作为标准。
AES 是目前较为主流的对称加密算法,兼具高安全性和高性能。AES 是由 NIST 选拔出的 Rijndael 算法作为标准,支持分组加密模式。AES 每次处理 128 位明文,生成相应的 128 位密文。对于较长的明文,需要通过分组迭代加密。
AES - ECB 模式 (不推荐)
加密一段包含 16 个字符的字符串,得到密文 A;然后把这段字符串复制一份成为一个32 个字符的字符串,再进行加密得到密文 B。我们验证下密文 B 是不是重复了一遍的密文 A。
模拟银行转账的场景,假设整个数据由发送方账号、接收方账号、金额三个字段构成。我们尝试改变密文中数据的顺序来操纵明文
private static final String KEY = "secretkey1234567";@GetMapping("ecb")public void ecb() throws Exception {Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");test(cipher, null);}private static SecretKeySpec setKey(String secret) {return new SecretKeySpec(secret.getBytes(), "AES");}private static void test(Cipher cipher, AlgorithmParameterSpec parameterSpec) throws Exception {cipher.init(Cipher.ENCRYPT_MODE, setKey(KEY), parameterSpec);System.out.println("一次:" + Hex.encodeHexString(cipher.doFinal("abcdefghijklmnop".getBytes())));System.out.println("两次:" + Hex.encodeHexString(cipher.doFinal("abcdefghijklmnopabcdefghijklmnop".getBytes())));byte[] sender = "1000000000012345".getBytes();byte[] receiver = "1000000000034567".getBytes();byte[] money = "0000000010000000".getBytes();//加密发送方账号System.out.println("发送方账号:" + Hex.encodeHexString(cipher.doFinal(sender)));//加密接收方账号System.out.println("接收方账号:" + Hex.encodeHexString(cipher.doFinal(receiver)));//加密金额System.out.println("金额:" + Hex.encodeHexString(cipher.doFinal(money)));byte[] result = cipher.doFinal(ByteUtils.concatAll(sender, receiver, money));//加密三个数据System.out.println("完整数据:" + Hex.encodeHexString(result));byte[] hack = new byte[result.length];//把密文前两段交换System.arraycopy(result, 16, hack, 0, 16);System.arraycopy(result, 0, hack, 16, 16);System.arraycopy(result, 32, hack, 32, 16);cipher.init(Cipher.DECRYPT_MODE, setKey(KEY), parameterSpec);//尝试解密System.out.println("原始明文:" + new String(ByteUtils.concatAll(sender, receiver, money)));System.out.println("操纵密文:" + new String(cipher.doFinal(hack)));}
输出
一次:a6025aaadd429e8c13073fc3512a7250
两次:a6025aaadd429e8c13073fc3512a7250a6025aaadd429e8c13073fc3512a7250
发送方账号:fdfc03515d95e2fa33edc9ca67cf43ae
接收方账号:e70eecf4baa8decf117d294e12d850c0
金额:f317ed23783f4babb607bd88ba076d0c
完整数据:fdfc03515d95e2fa33edc9ca67cf43aee70eecf4baa8decf117d294e12d850c0f317ed23783f4babb607bd88ba076d0c
原始明文:100000000001234510000000000345670000000010000000
操纵密文:100000000003456710000000000123450000000010000000
如上代码示例展示了 ECB 模式的漏洞,攻击者可以在不解密的情况下操纵密文,实现对敏感数据(如银行转账信息)的修改。 -----> 在不知道密钥的情况下,我们操纵密文实现了对明文数据的修改,对调了发送方账号和接收方账号
原始明文:100000000001234510000000000345670000000010000000
操纵密文:100000000003456710000000000123450000000010000000
代码运行的结果证明了:
-
重复的明文生成相同的密文。
-
攻击者可以通过调换密文分组的顺序,达到修改明文数据的效果。
-
重复性问题:如果明文中有重复的分组,密文中也会出现重复,这会暴露明文的模式,存在规律性。
-
独立分组问题:每个分组独立加密和解密,攻击者可以通过交换密文分组的顺序来操控明文内容。
因此,ECB 模式简单但不安全,不推荐使用。
AES - CBC 模式 (推荐)
CBC 模式,在解密或解密之前引入了 XOR 运算,第一个分组使用外部提供的初始化向量IV,从第二个分组开始使用前一个分组的数据,这样即使明文是一样的,加密后的密文也是不同的,并且分组的顺序不能任意调换。这就解决了 ECB 模式的缺陷.
把之前的代码修改为 CBC 模式,再次进行测试
private static final String initVector = "abcdefghijklmnop";@GetMapping("cbc")public void cbc() throws Exception {Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));test(cipher, iv);}
可以看到,相同的明文字符串复制一遍得到的密文并不是重复两个密文分组,并且调换密文分组的顺序无法操纵明文。
一次:6fa7a7b2c0979abecc1b59fe17b663c6
两次:6fa7a7b2c0979abecc1b59fe17b663c6e873cb4abb4b46b76cb748447373103c
发送方账号:ff4f74de614be6905951fa2ac68a529a
接收方账号:0dfdd3116d26dac4a7349167dfa0ce0a
金额:5521773b79160a1a51b9d8f8bfb0a346
完整数据:ff4f74de614be6905951fa2ac68a529abb54065906129619b122c978541f0076347086b16d09934e4f9d9dc4ab942af0
原始明文:100000000001234510000000000345670000000010000000
SD�A�x�%B[3t+B�Wi@��Cb��b�
GCM(Galois/Counter Mode)
AES-256-GCM简介
GCM(Galois/Counter Mode)
是一种结合计数器模式(Counter Mode
)和Galois
域认证的分组加密模式。它不仅能够提供高效的加密服务,还能实现消息认证(即验证消息的完整性和真实性)。与传统的CBC模式不同,GCM模式可以并行处理,极大提升了性能,特别适合高吞吐量的环境。
这是一种 AEAD
(Authenticated Encryption with Associated Data
)认证加密算法,除了能实现普通加密算法提供的保密性之外,还能实现可认证性和密文完整性,是目前最推荐的 AES 模式。
使用类似 GCM 的 AEAD 算法进行加解密,除了需要提供初始化向量和密钥之外,还可以提供一个 AAD(附加认证数据,additional authenticated data
),用于验证未包含在明文中的附加信息,解密时不使用加密时的 AAD 将解密失败。其实,GCM 模式的内部使用的就是 CTR 模式,只不过还使用了 GMAC 签名算法,对密文进行签名实现完整性校验。
AES-256-GCM工作原理
AES-256-GCM结合了AES-256加密算法和GCM模式,具备如下功能:
- 加密:数据通过AES-256加密算法被加密。
- 消息认证码(MAC):在加密的同时,GCM模式会生成一个128位的消息认证码,用于验证数据的完整性和真实性。这一特性可以防止数据被篡改。
- 附加数据:GCM支持附加认证数据(AAD),这部分数据不会被加密,但会被用于认证。例如,网络协议中的头部信息可以作为AAD进行保护。
安全优势
相较于其他常见的模式,如CBC(Cipher Block Chaining),AES-256-GCM提供了显著的优势:
- 并行处理:GCM模式允许多线程并行处理,加速了加密和解密过程,非常适合高性能需求的场景。
- 集成认证:GCM不仅加密数据,还生成认证标签,保证数据的完整性和真实性。相比之下,像CBC这样的模式需要单独实现消息认证。
- 防御重放攻击:GCM模式通过计数器的设计,有效防止了重放攻击和其他类似的攻击手段。
应用场景
AES-256-GCM的广泛应用场景包括:
- TLS/SSL加密:TLS 1.3推荐使用AES-GCM模式进行数据加密,以确保网络通信的安全性。
- VPN:许多VPN协议(如IPSec)使用AES-256-GCM进行数据传输加密。
- 云存储加密:云服务供应商在存储敏感数据时经常采用AES-256-GCM,以确保数据的安全性和完整性。
其他模式 和 敏感数据加密建议
除了 ECB 模式外,AES 还有 CBC、CFB、OFB 和 CTR 模式。推荐使用 CBC 或 CTR 模式。ECB 和 CBC 模式需要设置合适的填充方式来处理超过一个分组的数据。
此外,对于敏感数据加密,建议:
- 不要在代码中写死密钥或初始化向量(IV),应确保密钥和 IV 唯一、独立、且每次都变化。
- 使用独立的加密服务来管理密钥,避免将密钥与密文存储在同一个数据库中,确保加密服务有严格的管控标准。
- 数据库中不应保存明文敏感信息,可以存储脱敏数据,并在普通查询时使用脱敏信息。
Code
接下来,我们按照如上策略完成相关代码实现:
第一步,对于用户姓名和身份证,我们分别保存三个信息,脱敏后的明文、密文和加密ID。加密服务加密后返回密文和加密 ID,随后使用加密 ID 来请求加密服务进行解密
import lombok.Data;import javax.persistence.Entity;
import javax.persistence.Id;@Data
@Entity
public class UserData {@Idprivate Long id;private String idcard;//脱敏的身份证private Long idcardCipherId;//身份证加密IDprivate String idcardCipherText;//身份证密文private String name;//脱敏的姓名private Long nameCipherId;//姓名加密IDprivate String nameCipherText;//姓名密文
}
第二步,加密服务数据表保存加密 ID、初始化向量和密钥。加密服务表中没有密文,实现了密文和密钥分离保存.
import lombok.Data;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;import static javax.persistence.GenerationType.AUTO;@Data
@Entity
public class CipherData {@Id@GeneratedValue(strategy = AUTO)private Long id;private String iv;//初始化向量private String secureKey;//密钥
}
第三步,加密服务使用 GCM 模式( Galois/Counter Mode)的 AES-256 对称加密算法,也就是 AES-256-GCM
接下来,我们实现基于 AES-256-GCM
的加密服务,包含下面的主要逻辑:
- 加密时允许外部传入一个
AAD
用于认证,加密服务每次都会使用新生成的随机值作为密钥和初始化向量。 - 在加密后,加密服务密钥和初始化向量保存到数据库中,返回加密 ID 作为本次加密的标识。
- 应用解密时,需要提供加密 ID、密文和加密时的 AAD 来解密。加密服务使用加密 ID,从数据库查询出密钥和初始化向量。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;@Service
public class CipherService {//密钥长度public static final int AES_KEY_SIZE = 256;//初始化向量长度public static final int GCM_IV_LENGTH = 12;//GCM身份认证Tag长度public static final int GCM_TAG_LENGTH = 16;@Autowiredprivate CipherRepository cipherRepository;//内部加密方法public static byte[] doEncrypt(byte[] plaintext, SecretKey key, byte[] iv, byte[] aad) throws Exception {//加密算法Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");//Key规范SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");//GCM参数规范GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);//加密模式cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);//设置aadif (aad != null)cipher.updateAAD(aad);//加密byte[] cipherText = cipher.doFinal(plaintext);return cipherText;}//内部解密方法public static String doDecrypt(byte[] cipherText, SecretKey key, byte[] iv, byte[] aad) throws Exception {//加密算法Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");//Key规范SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");//GCM参数规范GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);//解密模式cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);//设置aadif (aad != null)cipher.updateAAD(aad);//解密byte[] decryptedText = cipher.doFinal(cipherText);return new String(decryptedText);}//加密入口public CipherResult encrypt(String data, String aad) throws Exception {//加密结果CipherResult encryptResult = new CipherResult();//密钥生成器KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");//生成密钥keyGenerator.init(AES_KEY_SIZE);SecretKey key = keyGenerator.generateKey();//IV数据byte[] iv = new byte[GCM_IV_LENGTH];//随机生成IVSecureRandom random = new SecureRandom();random.nextBytes(iv);//处理aadbyte[] aaddata = null;if (!StringUtils.isEmpty(aad))aaddata = aad.getBytes();//获得密文encryptResult.setCipherText(Base64.getEncoder().encodeToString(doEncrypt(data.getBytes(), key, iv, aaddata)));//加密上下文数据CipherData cipherData = new CipherData();//保存IVcipherData.setIv(Base64.getEncoder().encodeToString(iv));//保存密钥cipherData.setSecureKey(Base64.getEncoder().encodeToString(key.getEncoded()));cipherRepository.save(cipherData);//返回本地加密IDencryptResult.setId(cipherData.getId());return encryptResult;}//解密入口public String decrypt(long cipherId, String cipherText, String aad) throws Exception {//使用加密ID找到加密上下文数据CipherData cipherData = cipherRepository.findById(cipherId).orElseThrow(() -> new IllegalArgumentException("invlaid cipherId"));//加载密钥byte[] decodedKey = Base64.getDecoder().decode(cipherData.getSecureKey());//初始化密钥SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");//加载IVbyte[] decodedIv = Base64.getDecoder().decode(cipherData.getIv());//处理aadbyte[] aaddata = null;if (!StringUtils.isEmpty(aad))aaddata = aad.getBytes();//解密return doDecrypt(Base64.getDecoder().decode(cipherText.getBytes()), originalKey, decodedIv, aaddata);}
}
第四步,分别实现加密和解密接口用于测试。
可以让用户选择,如果需要保护二要素的话,就自己输入一个查询密码作为 AAD。系统需要读取用户敏感信息的时候,还需要用户提供这个密码,否则无法解密。这样一来,即使黑客拿到了用户数据库的密文、加密服务的密钥和 IV,也会因为缺少 AAD 无法解密.
@Autowiredprivate CipherService cipherService;// 加密 @GetMapping("right")public UserData right(@RequestParam(value = "name", defaultValue = "小工匠") String name,@RequestParam(value = "idcard", defaultValue = "300000000000001234") String idCard,@RequestParam(value = "aad", required = false) String aad) throws Exception {UserData userData = new UserData();userData.setId(1L);//脱敏姓名userData.setName(chineseName(name));//脱敏身份证userData.setIdcard(idCard(idCard));//加密姓名CipherResult cipherResultName = cipherService.encrypt(name, aad);userData.setNameCipherId(cipherResultName.getId());userData.setNameCipherText(cipherResultName.getCipherText());//加密身份证CipherResult cipherResultIdCard = cipherService.encrypt(idCard, aad);userData.setIdcardCipherId(cipherResultIdCard.getId());userData.setIdcardCipherText(cipherResultIdCard.getCipherText());return userRepository.save(userData);}// 解密 @GetMapping("read")public void read(@RequestParam(value = "aad", required = false) String aad) throws Exception {UserData userData = userRepository.findById(1L).get();log.info("name : {} idcard : {}",cipherService.decrypt(userData.getNameCipherId(), userData.getNameCipherText(), aad),cipherService.decrypt(userData.getIdcardCipherId(), userData.getIdcardCipherText(), aad));}// 脱敏身份证private static String idCard(String idCard) {String num = StringUtils.right(idCard, 4);return StringUtils.leftPad(num, StringUtils.length(idCard), "*");}// 脱敏姓名public static String chineseName(String chineseName) {String name = StringUtils.left(chineseName, 1);return StringUtils.rightPad(name, StringUtils.length(chineseName), "*");}
启动服务,访问 http://localhost:45678/storeidcard/right
访问解密接口: http://localhost:45678/storeidcard/read
如果AAD错误
经过这样的设计,二要素就比较安全了。黑客要查询用户二要素的话,需要同时拿到密文、IV+ 密钥、AAD。而这三者可能由三方掌管,要全部拿到比较困难。
相关文章:

加密与安全_优雅存储二要素(AES-256-GCM )
文章目录 什么是二要素如何保护二要素(姓名和身份证)加密算法分类场景选择算法选择AES - ECB 模式 (不推荐)AES - CBC 模式 (推荐)GCM(Galois/Counter Mode)AES-256-GCM简介AES-256-GCM工作原理安全优势 应用场景其他模式 和 敏感…...

【C++高阶】解锁C++的深层魅力——探索特殊类的奥秘
📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C “ 登神长阶 ” 🤡往期回顾🤡:C 类型转换 🌹🌹期待您的关注 🌹🌹 ❀C特殊类 📒1. 不能被拷贝…...

Vue学习记录之三(ref全家桶)
ref、reactive是在 setup() 声明组件内部状态用的, 这些变量通常都要 return 出去,除了供 < template > 或渲染函数渲染视图,也可以作为 props 或 emit 参数 在组件间传递。它们的值变更可触发页面渲染。 ref :是一个函数&…...

第二十六篇——九地篇:九种形势的应对之道
目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么? 四、总结五、升华 一、背景介绍 地势的维度重新阐述了懂得人心的重要性,道久其归一为为别人。…...
学习记录:js算法(三十七): 搜索二维矩阵
文章目录 搜索二维矩阵我的思路网上思路 总结 搜索二维矩阵 给你一个满足下述两条属性的 m x n 整数矩阵: ● 每行中的整数从左到右按非严格递增顺序排列。 ● 每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中&a…...

拥控算法BBR入门1
拥塞控制算法只与本地有关 一个TCP会话使用的拥塞控制算法只与本地有关。 两个TCP系统可以在TCP会话的两端使用不同的拥塞控制算法 Bottleneck Bandwidth and Round-trip time Bottleneck 瓶颈 BBR models the network to send as fast as the available bandwidth and is 2…...

[Python数据可视化]Plotly Express: 地图数据可视化的魅力
在数据分析和可视化的世界中,地图数据可视化是一个强大而直观的工具,它可以帮助我们更好地理解和解释地理数据。Python 的 Plotly Express 库提供了一个简单而强大的方式来创建各种地图。本文将通过一个简单的示例,展示如何使用 Plotly Expre…...
windows C++ 并行编程-PPL 中的取消操作(四)
并行模式库 (PPL) 中取消操作的角色、如何取消并行工作以及如何确定取消并行工作的时间。 运行时使用异常处理实现取消操作。 请勿在代码中捕捉或处理这些异常。 此外,还建议你在任务的函数体中编写异常安全的代码。 例如,可以使用获取资源即初始化 (RA…...

【数据结构】字符串与JSON字符串、JSON字符串及相应数据结构(如对象与数组)之间的相互转换
前言: 下面打印日志用的是FastJSON依赖库中的 Log4j2。依赖: <!-- Alibaba Fastjson --> <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.80</version> …...

LeetcodeTop100 刷题总结(一)
LeetCode 热题 100:https://leetcode.cn/studyplan/top-100-liked/ 文章目录 一、哈希1. 两数之和49. 字母异位词分组128. 最长连续序列 二、双指针283. 移动零11. 盛水最多的容器15. 三数之和42. 接雨水(待完成) 三、滑动窗口3. 无重复字符的…...

Next-ViT: 下一代视觉Transformer,用于现实工业场景中的高效部署
摘要 由于复杂的注意力机制和模型设计,大多数现有的视觉Transformer(ViTs)在实际的工业部署场景中,如TensorRT和CoreML,无法像卷积神经网络(CNNs)那样高效运行。这提出了一个明显的挑战&#x…...
C++知识点示例代码助记
C语言设计期末知识点附示例代码。 1. 基础语法 变量和数据类型: int a 10; // 整型 float b 5.25f; // 单精度浮点型 double c 5.25; // 双精度浮点型 char d A; // 字符型 bool e true; // 布尔型 const int PI 3.14; // 常量输入输出&…...

Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 垃圾回收算法
文章目录 垃圾回收机制垃圾判断算法引用计数法可达性分析算法虚拟机栈中的引用(方法的参数、局部变量等)本地方法栈中 JNI 的引用类静态变量运行时常量池中的常量 垃圾收集算法Mark-Sweep(标记-清除)算法Copying(标记-…...

苍穹外卖Day01-2
导入接口文档 yApi接口管理平台http://api.doc.jiyou-tech.com/ 创建项目 导入接口文件 导入结果界面 Swagger 介绍 使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,就可以做到生成接口文档,以及在线接口调试页面。 官网:ht…...

软考中级软件设计师——数据结构与算法基础学习笔记
软考中级软件设计师——数据结构与算法基本概念 什么是数据数据元素、数据项数据结构逻辑结构物理结构(存储结构) 算法什么是算法五个特性算法效率的度量时间复杂度空间复杂度 什么是数据 数据是信息的载体,是描述客观事物属性的数、字符及所…...

虚幻引擎 | (类恐鬼症)玩家和NPC语音聊天(中)
虚幻引擎 | (类恐鬼症)玩家和NPC语音聊天-CSDN博客 上篇偏重实现步骤,中篇偏重理解校准和降低延迟,下篇加入上下文背景array和设置口音 TTS通用参数 ————————————————————————————————————…...

整流电路的有源逆变工作状态
目录 1. 逆变的概念 2. 有源逆变的条件 3. 电流电路的概念 4. 产生逆变的条件 5. 三相桥式全控整流电路的有源逆变工作状态 6. 逆变角的概念 7. 逆变失败的原因 8. 最小逆变角的限制 整流电路的有源逆变状态是指通过控制整流器,使其将直流电源的能量反向送回…...

Android 签名、空包签名 、jarsigner、apksigner
jarsigner是JDK提供的针对jar包签名的通用工具, 位于JDK/bin/jarsigner.exe apksigner是Google官方提供的针对Android apk签名及验证的专用工具, 位于Android SDK/build-tools/SDK版本/apksigner.bat jarsigner: jarsigner签名空包执行的命令: jar…...
java基础(小技巧)
文章目录 一、日志输出二、字符串拼接三、日期比较四、常用注解五、Lombok的原理 提示:以下是本篇文章正文内容,下面案例可供参考 一、日志输出 之前使用的方式。在要使用的类里面定义日志类: private static Logger logger LoggerFactory…...

Android Studio 安装配置教程(Windows最详细版)
目录 前言 Android Studio 下载 Android Studio 安装 Android Studio 使用 一、创建默认项目(Compose) 二、创建常规项目 三、使用ViewBinding 四、查看Gradle版本、SDK版本、JDK版本 ① Gradle版本 ② SDK版本 ③ JDK版本 前言 Android开发…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
高防服务器价格高原因分析
高防服务器的价格较高,主要是由于其特殊的防御机制、硬件配置、运营维护等多方面的综合成本。以下从技术、资源和服务三个维度详细解析高防服务器昂贵的原因: 一、硬件与技术投入 大带宽需求 DDoS攻击通过占用大量带宽资源瘫痪目标服务器,因此…...