首页 » SEO优化 » svnupphp技巧_数据加解密常识入门

svnupphp技巧_数据加解密常识入门

访客 2024-11-07 0

扫一扫用手机浏览

文章目录 [+]

若有描述缺点的地方,欢迎小伙伴们批评示正。

如果对您有帮助,希望点个关注和赞再走哦!

svnupphp技巧_数据加解密常识入门

目录

一、加解密根本知识1、算法分类1)非对称加密2)对称加密3)单向加密2、常用加解密算法实现二、数字证书三、PKI 体系四、常见技能方案1、AES对称加解密2、RSA非对称加解密3、AES+RSA稠浊加解密五、代码示例1、接口出入参的加解密1)办法一:AES加密2)办法二:AES+RSA稠浊加密2、文件加解密3、跨措辞调用加解密附录、参考资料&在线工具一、加解密根本知识1、算法分类

小郭目前理解到常用的加密算法紧张分为三种思路:对称加密、非对称加密、单向加密。

svnupphp技巧_数据加解密常识入门
(图片来自网络侵删)
1)非对称加密

非对称加密算法是一种密钥的保密方法。
利用它须要准备两个密钥:公开密钥(publickey: 简称公钥)和私有密钥(privatekey: 简称私钥)。
公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密,反之如果用私钥对数据加密(不推举),则只有对应的公钥才能解密。

由于加密和解密利用的是两个不同的密钥,以是这种算法被称为非对称加密算法。
常用非对称加密算法有DSA、RSA等,个中RSA 是主流的非对称加密算法 。

RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。
当时他们三人都在麻省理工学院事情。
RSA便是他们三人姓氏开头字母拼在一起组成的。

RSA的两个密钥都可以用来加密,解密时须要利用另一个密钥。
但是,常日我们只用公钥加密私钥解密这种办法,由于公钥是公开的,如果用私钥加密数据,大家就都可以利用公开的公钥解密,也就没有保密可言了。
理论上如果A和B之间要完备通过RSA实现保密相互通信,须要A和B各自天生一组密钥,同时各自保管好自己的私钥,然后用对方给的公钥加密要发送的数据,用自己的私钥解密对方发送的。

RSA的优点是:安全 ;它的缺陷是:加密速率慢。
RSA 是第一个能同时用于 加密 和 数字署名 的算法,它能够 抵抗 到目前为止已知的 所有密码攻击,已被 ISO 推举为公钥数据加密标准。

RSA的流程大致是如下这样:

客户端准备公钥A、私钥A,做事端也准备一套公钥B、私钥B。
首先相互奉告对方准备的公钥。
客户端向做事器发送: 客户端用做事端给的公钥B加密信息,发送给做事端,做事端再用自己的私钥B解密做事器向客户端发送:做事端用客户端给的公钥A加密信息,发送给客户端,客户端再用自己的私钥A解密

大略来说:「公钥加密、私钥解密、私钥署名、公钥验签」

看到这里有些小伙伴肯定会和我一样提出质疑:公钥和私钥透露了怎么办? 要保障密钥的安全,有很多办法,比如数字证书,证书署名等等,这些在后续的学习中都会涉及到。

2)对称加密

利用相同的密钥进行加密和解密的过程便是对称加密。
加密秘钥和解密秘钥是一样,当密钥被别人知道后,就相称于没有加密了。

常用的对称加密算法有DES、AES等,个中AES 是主流的对称加密算法。

AES是一种可逆的对称加密算法,这类算法在加密和解密时利用相同的密钥,或是利用两个可以大略地相互推算的密钥,一样平常用于做事端对做事端之间对数据进行加密解密。
它是一种为了替代原来DES、3DES而建立的高等加密标准(Advanced Encryption Standard)。
作为可逆且对称的块加密,AES加密算法的速率比RSA加密等非对称加密算法快很多,在很多场合都须要AES对称加密,但是哀求加密端和解密端双方都利用相同的密钥是AES算法的紧张缺陷之一。
它的优点:加密速率快;它的缺陷:如果密钥丢失,就随意马虎解密密文,安全性相比拟较差。

虽然非对称加密更加安全,但是对称加密算法比非对称加密算法快大约1500倍。
所有拥有更大的性能上风。
在实际过程中,一样平常是将对称加密和非对称加密相结合进行利用。
比如将前面提到的RSA和AES结合利用,先用AES密钥对数据进行加密,然后用RSA的公钥对AES密钥进行加密或者署名。

3)单向加密

单向加密指只能加密数据,而不能解密数据。
常用的有MD5,SHA系列算法。

小结一下,上面三种加密理论分别都有多种不同的算法实现,他们紧张的用场在于:

非对称加密:身份验证

对称加密:数据的机密性

单向加密:数据的完全性

2、常用加解密算法实现

加密解密的操作终极都是为了提升数据安全性,安全性的差别在于利用什么加密解密办法,制订什么样的加密策略。

1)常用对称加密算法有:

AESDES3DESPBE

2)常用非对称加密算法有:

RSADSAECC

3)常用单向加密(不可逆加密、天生择要)算法有:

MD5SHAHMAC二、数字证书

对付非对称加密算法和数字署名来说,很主要的步骤便是公钥的分发。
理论上任何人都可以获取到公开的公钥,然而这个公钥文件有可能是假造的,传输过程中也有可能被修改,以是一旦公钥自身出了问题,则全体建立在其上的的安全性将不复成立。

数字证书机制 用于办理公钥分发安全性问题,确保所记录信息的合法性。
比如证明某个公钥是某个实体(个人或组织)拥有,并且确保任何修改都能被检测出来,从而实现对用户公钥的安全分发。

做事器拥有公开密钥,它把公钥交给认证机构,机构给他的公钥上打上数字署名并发一个证书,表示这个公钥是正规的。
当做事器把公钥给客户端之后,客户端还要通过署名验证公钥的真实性。

根据所保护公钥的用场,数字证书可以分为 加密数字证书(Encryption Certificate) 和 署名验证数字证书(Signature Certificate)。

加密数字证书 用来保护加密用场的公钥署名验证数字证书 用来保护署名用场的公钥

上面这两种类型的公钥可以同时放在同一证书中存储。

一样平常情形下,证书须要由 证书认证机构(Certification Authority,CA) 来进行签发和背书。

威信的商业证书认证机构包括 DigiCert、GlobalSign、VeriSign 等用户也可以自行搭建本地 CA 系统,在私有网络中进行自有证书的签发和背书

一个数字证书内容可能包括证书域(证书的版本、序列号、署名算法类型、签发者信息、有效期、被签发主体、签发的公开密钥)、CA 对证书的署名算法和署名值等,他们一样平常采取 X.509 规范编写。
那么X.509规范又是什么呢?

X.509 证书规范

X.509 是密码学里公钥证书的格式标准。

证书格式有如下几种分类:

证书文件的文件名后缀一样平常为 .crt 或 .cer对应私钥文件的文件名后缀一样平常为 .key证书要求文件的文件名后缀为 .csr有时候也统一用 .pem 作为文件名后缀

X.509 规范中一样平常推举利用 PEM(Privacy Enhanced Mail)格式来存储证书干系的文件。

PEM 格式 采取文本办法进行存储,一样平常包括首尾标记和内容块,内容块采取 Base64 格式编码,示例如下:

-----BEGIN CERTIFICATE-----BASE64 CONTENT-----END CERTIFICATE-----

按照 X.509 规范,公钥可以通过证书机制来进行保护,但证书的天生、分发、撤销等步骤并未涉及,接下来要讲的PKI体系中定义了这些步骤须要遵照标准。

三、PKI 体系

PKI(Public Key Infrastructure)体系 办理了证诗人命周期干系的认证和管理问题,定义了安全地管理、分发证书须要遵照的标准。

PKI 是建立在公私钥根本上实现安全可靠通报和身份确认的一个通用框架,并不代表某个特定的密码学技能和流程,实现了 PKI 规范的平台可以安全可靠地管理网络中用户的密钥和证书。

一个完备 PKI 体系该当包括如下组件:

数字证书:包含了用于署名和加密数据的公钥的电子凭据,是 PKI 的核心元素认证中央(CA):数字证书的申请及签发机关,CA 必须具备威信性证书资料库:存储已签发的数字证书和公钥,以及干系证书目录,用户可由此得到所需的其他用户的证书及公钥证书吊销列表(CRL)/OCSP:在有效期内吊销的证书列表,OCSP(在线证书状态协议)是得到证书状态的国际协议密钥备份及规复:为避免因用户丢失解密密钥而无法解密合法数据的情形,PKI 应供应备份与规复密钥的机制PKI 运用接口(API):为运用供应安全、同等、 可信的办法与 PKI 交互四、常见技能方案1、AES对称加解密

运用开拓中一样平常利用AES 密钥对接口要乞降相应内容进行加密,密文无法被第三方识别,从而防止接口传输数据透露。

2、RSA非对称加解密

RSA 密钥的场景一样平常是对接口要乞降相应内容进行署名,以确认接口传输的内容没有被修改。
不论接口内容是明文还是密文,RSA 均可正常署名。

3、AES+RSA稠浊加解密

小郭熟习的一种常见的稠浊办法是:

1)第一步:客户端利用随机天生的AES密钥对业务数据进行加密2)第二步:客户端将AES密钥利用RSA公钥进行加密3)第三步:客户端将加密后的AES密钥和业务数据一并提交给做事端4)做事端利用RSA私钥解密AES密钥,然后利用解密的AES密钥对业务数据进行解密5)业务处理完成后,做事端动态天生一个新的AES密钥6)做事端利用新的AES密钥对业务处理结果数据进行加密,同时利用RSA私钥对该AES密钥进行署名7)客户端收到返回数据后, 先用RSA公钥对AES密钥署名进行验证,验证通过后利用AES密钥对业务数据进行解密。

目前支付宝开放平台采取的也是AES+RSA稠浊办法,和我在上面举的例子有一点不同的是:支付宝的办法是让开发者对要求参数先做 AES对称加密,然后对加密后的密文进行 RSA 署名(防止数据在传输过程被修改)。

五、代码示例

声明:部分代码借鉴于网络,加解密的代码基本都是相似的。

1、接口出入参的加解密

小郭在这里以常用的springboot运用程序接口来示例如何做数据加解密,这里分别演示AES加密、AES+RSA稠浊加密办法(加强版)。

1)办法一:AES加密

整体思路:

要求:1.客户端提交base64编码的aeskey2.做事端从要求头获取aeskey进行base64解码,3.做事端利用base64解码后的aeskey对入参进行解密,并进行后续业务处理相应:1.做事端随机天生新的aeskey,2.做事端利用aeskey进行业务结果加密,对业务加密结果再进行base64编码。
3.做事端将aeskey进行base64加密,4.做事端返回加密的aeskey和业务结果数据。
5.客户端利用base64对aeskey和业务结果数据进行解码6.客户端利用aeskey对业务结果数据进行解密

a. 定义两个代表开启加密解密功能的表明:

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD,ElementType.TYPE,ElementType.PARAMETER})public @interface Decrypt {}

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface Encrypt {}

b. AES加解密算法核心工具类:

package com.gyd.encrypt.v1;import org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;import java.security.SecureRandom;import java.util.Base64;import java.util.Random;public class AESUtils { / 加密算法AES / private static final String KEY_ALGORITHM = "AES"; / key的长度,Wrong key size: must be equal to 128, 192 or 256 传入时须要16、24、36 / private static final Integer KEY_LENGTH = 16 8; / 算法名称/加密模式/数据添补办法 默认:AES/ECB/PKCS5Padding / private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; / 后端AES的key,由静态代码块赋值 / public static String key; / 不能在代码中创建 JceSecurity.getVerificationResult 会将其put进 private static final Map<Provider,Object>中,导致内存缓便被耗尽 / private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider(); static { key = getKey(); } / 获取key / public static String getKey() { StringBuilder uid = new StringBuilder(); //产生16位的强随机数 Random rd = new SecureRandom(); for (int i = 0; i < KEY_LENGTH / 8; i++) { //产生0-2的3位随机数 int type = rd.nextInt(3); switch (type) { case 0: //0-9的随机数 uid.append(rd.nextInt(10)); break; case 1: //ASCII在65-90之间为大写,获取大写随机 uid.append((char) (rd.nextInt(25) + 65)); break; case 2: //ASCII在97-122之间为小写,获取小写随机 uid.append((char) (rd.nextInt(25) + 97)); break; default: break; } } return uid.toString(); } // 获取 cipher private static Cipher getCipher(byte[] key, int model) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec(key, KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM,PROVIDER); cipher.init(model, secretKeySpec); return cipher; } // AES加密 public static String encrypt(byte[] data, byte[] key) throws Exception { Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE); return Base64.getMimeEncoder().encodeToString(cipher.doFinal(data)); } // AES解密 public static byte[] decrypt(byte[] data, byte[] key) throws Exception { Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE); return cipher.doFinal(Base64.getMimeDecoder().decode(data)); }}

c. springboot接口出参统一拦截处理(实现出参加密):

package com.gyd.encrypt.v1;import com.fasterxml.jackson.databind.ObjectMapper;import com.gyd.encrypt.ResultWrapper;import com.gyd.encrypt.annotation.Encrypt;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.Base64;/ @ClassName EncryptResponse @Description 要求相应加密(AES) @Author guoyading @Version 1.0 /@ControllerAdvicepublic class EncryptResponse implements ResponseBodyAdvice<ResultWrapper> { private ObjectMapper om = new ObjectMapper(); @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return returnType.hasMethodAnnotation(Encrypt.class); } @Override public ResultWrapper beforeBodyWrite(ResultWrapper body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { String aesKey = AESUtils.getKey(); byte[] keyBytes = aesKey.getBytes(); try { body.setAesKey(Base64.getMimeEncoder().encodeToString(aesKey.getBytes())); if (body.getData() != null) { body.setData(AESUtils.encrypt(om.writeValueAsBytes(body.getData()), keyBytes)); } } catch (Exception e) { e.printStackTrace(); } return body; }}

d. springboot接口入参统一拦截处理(入参解密):

package com.gyd.encrypt.v1;import com.gyd.encrypt.annotation.Decrypt;import org.springframework.core.MethodParameter;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpInputMessage;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Type;import java.util.Base64;/ @ClassName DecryptRequest @Description 要求入参解密(AES) @Author guoyading @Version 1.0 /@ControllerAdvicepublic class DecryptRequest extends RequestBodyAdviceAdapter { @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class); } @Override public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { byte[] body = new byte[inputMessage.getBody().available()]; //获取约定的AES密钥 String aesKey = inputMessage.getHeaders().get("aesKey").get(0); if (StringUtils.isEmpty(aesKey)) { throw new RuntimeException("鉴权失落败"); } inputMessage.getBody().read(body); try { byte[] decrypt = AESUtils.decrypt(body, Base64.getMimeDecoder().decode(aesKey)); final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt); return new HttpInputMessage() { @Override public InputStream getBody() { return bais; } @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } }; } catch (Exception e) { e.printStackTrace(); } return super.beforeBodyRead(inputMessage, parameter, targetType, converterType); }}

e. 接口出参统一包装工具:

package com.gyd.encrypt;import java.io.Serializable;public class ResultWrapper<T> implements Serializable { private static final long serialVersionUID = 1L; private int status; private String aesKey; private String sign; private T data; private String message; public ResultWrapper() { this.status = StatusCode.OK.value(); this.message = null; } public ResultWrapper(String message) { this.status = StatusCode.FAILURE.value(); this.message = message; } public String getAesKey() { return aesKey; } public void setAesKey(String aesKey) { this.aesKey = aesKey; } public static ResultWrapper<?> SUCCESS() { return new ResultWrapper(); } public static <R> ResultWrapper<R> SUCCESS(R value) { return (new ResultWrapper()).setData(value); } public static ResultWrapper<?> FAILURE(String message) { return new ResultWrapper(message); } public static ResultWrapper<?> FORBIDDEN() { ResultWrapper<?> result = new ResultWrapper(); result.setStatus(StatusCode.NOAUTHORITY.value()); result.setMessage("forbidden"); return result; } public int getStatus() { return this.status; } public ResultWrapper<T> setStatus(int status) { this.status = status; return this; } public T getData() { return this.data; } public ResultWrapper<T> setData(T data) { this.data = data; return this; } public String getMessage() { return this.message; } public ResultWrapper<T> setMessage(String message) { this.message = message; return this; } public String getSign() { return sign; } public void setSign(String sign) { this.sign = sign; }}

public enum StatusCode { OK(200), FAILURE(300), ERROR(500), UNLOGIN(401), BADREQUEST(400), NOAUTHORITY(403), AUTHREQUIRED(407), NOTFOUND(404); private final Integer code; private StatusCode(Integer code) { this.code = code; } public Integer value() { return this.code; } public static StatusCode value(int code) { StatusCode[] var1 = values(); int var2 = var1.length; for(int var3 = 0; var3 < var2; ++var3) { StatusCode value = var1[var3]; if (value.code == code) { return value; } } return null; }}

f. 测试接口定义:

package com.gyd.controller;import com.gyd.dto.User;import com.gyd.encrypt.ResultWrapper;import com.gyd.encrypt.annotation.Decrypt;import com.gyd.encrypt.annotation.Encrypt;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiParam;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/ @ClassName AES对称加密示例 @Author guoyading @Version 1.0 /@RestController@RequestMapping("/user2")@Api(tags="用户数据操作干系接口(AES加解密测试)")public class AESEncryptController { @ApiOperation("新增用户接口") @PostMapping("/save") @Decrypt public ResultWrapper<User> save(@ApiParam @RequestBody User data){ return ResultWrapper.SUCCESS(data); } @ApiOperation("查询用户接口") @PostMapping("/query") @Encrypt public ResultWrapper<User> query(){ User user = new User(); user.setId(1); user.setUsername("张三"); return ResultWrapper.SUCCESS(user); }}

g. 接口入参加解密测试:

要求接口:localhost:8082/user2/save

要求入参:

先将接口入参用客户端随机天生的AES密钥进行加密:

AES密钥明文是: xxxx.gydblog.com

通过base64编码后是eHh4eC5neWRibG9nLmNvbQ==,须要在要求时放入要求头中,字段名是:aesKey

//原始入参{"id": 1,"username": "张三"}//加密后入参yZ3sYBQ6zdNUNBKIcjQ8bEqbC7voBKlbU0lJmkLhRcY=

要求出参:

{"status": 200,"aesKey": null,"sign": null,"data": {"id": 1,"username": "张三"},"message": null}

h. 接口出参加密测试:

要求接口:localhost:8082/user2/query

要求入参:无

要求出参:

{"status": 200,"aesKey": "MUFER1M2RWpuTDVwYjh4NA==","sign": null,"data": "Kxbvbkeslxm9YL/BgnaFkEJADZsObwjs8tjGbgOlIdw=","message": null}

对要求出参中的aesKey和data分别利用base64在线工具进行解码, 然后用解码后的aesKey对解码后的data数据进行解密即可得到终极业务数据。

2)办法二:AES+RSA稠浊加密

a. 定义两个代表稠浊加解密的表明

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD,ElementType.TYPE,ElementType.PARAMETER})public @interface MixedDecrypt {}

@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface MixedEncrypt {}

b.RSA核心算法工具类:

package com.gyd.encrypt.v2;import java.util.Base64;import javax.crypto.Cipher;import java.io.ByteArrayOutputStream;import java.nio.charset.StandardCharsets;import java.security.;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;/ @description: RSA加密、解密算法工具类 @param: @return: /public class RSAUtils { / 加密算法RSA / private static final String KEY_ALGORITHM = "RSA"; / 算法名称/加密模式/数据添补办法 默认:RSA/ECB/PKCS1Padding / private static final String ALGORITHMS = "RSA/ECB/PKCS1Padding"; / Map获取公钥的key / private static final String PUBLIC_KEY = "publicKey"; / Map获取私钥的key / private static final String PRIVATE_KEY = "privateKey"; / RSA最大加密明文大小 / private static final int MAX_ENCRYPT_BLOCK = 245; / RSA最大解密密文大小 / private static final int MAX_DECRYPT_BLOCK = 256; / 1024 117 128 RSA 位数 如果采取2048 上面最大加密和最大解密则须填写: 245 256 / private static final int INITIALIZE_LENGTH = 2048; / 后端RSA的密钥对(公钥和私钥)Map,由静态代码块赋值 / private static Map<String, Object> genKeyPair = new LinkedHashMap<>(); static { try { genKeyPair.putAll(genKeyPair()); } catch (Exception e) { // 输出到日志文件中 System.err.println(e.getMessage()); } } / 天生密钥对(公钥和私钥) / private static Map<String, Object> genKeyPair() throws Exception { System.out.println("-------------------开始天生密钥对"); KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); keyPairGen.initialize(INITIALIZE_LENGTH); KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map<String, Object> keyMap = new HashMap<>(2); //公钥 keyMap.put(PUBLIC_KEY, publicKey); //私钥 keyMap.put(PRIVATE_KEY, privateKey); return keyMap; } / 私钥解密 @param encryptedData 已加密数据 @param privateKey 私钥(BASE64编码) / public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception { //base64格式的key字符串转Key工具 Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(privateKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.DECRYPT_MODE, privateK); //分段进行解密操作 return encryptAndDecryptOfSubsection(encryptedData, cipher, MAX_DECRYPT_BLOCK); } / 公钥加密 @param data 源数据 @param publicKey 公钥(BASE64编码) / public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { //base64格式的key字符串转Key工具 Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(publicKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.ENCRYPT_MODE, publicK); //分段进行加密操作 return encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK); } / 私钥加密 @param data 源数据 @param privateKey 私钥(BASE64编码) / public static String encryptByPrivateKey(byte[] data, String privateKey) throws Exception { Key privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(privateKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.ENCRYPT_MODE, privateK); return Base64.getMimeEncoder().encodeToString(encryptAndDecryptOfSubsection(data, cipher, MAX_ENCRYPT_BLOCK)); } / 公钥解密 @param encryptedData 已加密数据 @param publicKey 公钥(BASE64编码) / public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception { Key publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(Base64.getMimeDecoder().decode(publicKey))); Cipher cipher = Cipher.getInstance(ALGORITHMS); cipher.init(Cipher.ENCRYPT_MODE, publicK); return encryptAndDecryptOfSubsection(encryptedData, cipher, MAX_ENCRYPT_BLOCK); } / 获取私钥 / public static String getPrivateKey() { Key key = (Key) genKeyPair.get(PRIVATE_KEY); return Base64.getMimeEncoder().encodeToString(key.getEncoded()); } / 获取公钥 / public static String getPublicKey() { Key key = (Key) genKeyPair.get(PUBLIC_KEY); return Base64.getMimeEncoder().encodeToString(key.getEncoded()); } / 分段进行加密、解密操作 / private static byte[] encryptAndDecryptOfSubsection(byte[] data, Cipher cipher, int encryptBlock) throws Exception { int inputLen = data.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 对数据分段加密 while (inputLen - offSet > 0) { if (inputLen - offSet > encryptBlock) { cache = cipher.doFinal(data, offSet, encryptBlock); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i encryptBlock; } out.close(); return out.toByteArray(); } / 用私钥对信息天生数字署名 @param data 已加密数据 @param privateKey 私钥(BASE64编码) / public static String sign(byte[] data, String privateKey) throws Exception { byte[] keyBytes = Base64.getMimeDecoder().decode(privateKey); PrivateKey privateK = KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); Signature signature = Signature.getInstance("MD5withRSA"); signature.initSign(privateK); signature.update(data); return Base64.getMimeEncoder().encodeToString(signature.sign()); } / 校验数字署名 @param data 已加密数据 @param publicKey 公钥(BASE64编码) @param sign 数字署名 / public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { byte[] keyBytes = Base64.getMimeDecoder().decode(publicKey); PublicKey publicK = KeyFactory.getInstance(KEY_ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes)); Signature signature = Signature.getInstance("MD5withRSA"); signature.initVerify(publicK); signature.update(data); return signature.verify(Base64.getMimeDecoder().decode(sign)); }}

c. springboot接口入参统一拦截处理(入参解密)

package com.gyd.encrypt.v2;import com.gyd.encrypt.annotation.MixedDecrypt;import com.gyd.encrypt.v1.AESUtils;import org.springframework.core.MethodParameter;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpInputMessage;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Type;import java.util.Base64;/ @ClassName DecryptRequest @Description 要求解密(AES+RSA) @Author guoyading @Date 2023/11/22 15:24 @Version 1.0 /@ControllerAdvicepublic class MixedDecryptRequest extends RequestBodyAdviceAdapter { @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.hasMethodAnnotation(MixedDecrypt.class) || methodParameter.hasParameterAnnotation(MixedDecrypt.class); } @Override public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { byte[] body = new byte[inputMessage.getBody().available()]; //获取客户真个AES密钥 String aesKeyStr = inputMessage.getHeaders().get("aesKey").get(0); if (StringUtils.isEmpty(aesKeyStr)) { throw new RuntimeException("鉴权失落败"); } inputMessage.getBody().read(body); try { //利用RSA私钥对aesKey进行解密 byte[] aesKey = RSAUtils.decryptByPrivateKey(Base64.getMimeDecoder().decode(aesKeyStr),RSAUtils.getPrivateKey()); //利用解密后的aeskey对要求数据进行解密 byte[] decrypt = AESUtils.decrypt(body,aesKey); final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt); return new HttpInputMessage() { @Override public InputStream getBody() { return bais; } @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } }; } catch (Exception e) { e.printStackTrace(); } return super.beforeBodyRead(inputMessage, parameter, targetType, converterType); }}

d.springboot接口入参统一拦截处理(出参加密):

package com.gyd.encrypt.v2;import com.fasterxml.jackson.databind.ObjectMapper;import com.gyd.encrypt.ResultWrapper;import com.gyd.encrypt.annotation.MixedEncrypt;import com.gyd.encrypt.v1.AESUtils;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import java.util.Base64;/ @ClassName EncryptResponse @Description 相应加密(AES+RSA) @Author guoyading @Version 1.0 /@ControllerAdvicepublic class MixedEncryptResponse implements ResponseBodyAdvice<ResultWrapper> { private ObjectMapper om = new ObjectMapper(); @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return returnType.hasMethodAnnotation(MixedEncrypt.class); } @Override public ResultWrapper beforeBodyWrite(ResultWrapper body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { //随机天生aeskey String aesKey = AESUtils.getKey(); byte[] keyBytes = aesKey.getBytes(); try { //对aesKey进行base64编码 body.setAesKey(Base64.getMimeEncoder().encodeToString(aesKey.getBytes())); // 对key 用私钥加签 body.setSign(RSAUtils.sign(aesKey.getBytes(), RSAUtils.getPrivateKey())); if (body.getData() != null) { body.setData(AESUtils.encrypt(om.writeValueAsBytes(body.getData()), keyBytes)); } } catch (Exception e) { e.printStackTrace(); } return body; }}

e.测试接口定义

package com.gyd.controller;import com.gyd.dto.CheckInfo;import com.gyd.dto.User;import com.gyd.encrypt.ResultWrapper;import com.gyd.encrypt.annotation.MixedDecrypt;import com.gyd.encrypt.annotation.MixedEncrypt;import com.gyd.encrypt.v2.RSAUtils;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import org.apache.tomcat.util.codec.binary.Base64;import org.springframework.web.bind.annotation.;@RestController@RequestMapping("/user3")@Api(tags="用户数据操作干系接口(AES+RSA稠浊加解密测试)")public class AESRSAEncryptController { @ApiOperation("新增用户接口") @PostMapping("/save") @MixedDecrypt public ResultWrapper<User> save(@RequestBody User user){ return ResultWrapper.SUCCESS(user); } @ApiOperation("查询用户接口") @PostMapping("/query") @MixedEncrypt public ResultWrapper<User> query(){ User user = new User(); user.setId(1); user.setUsername("张三"+Math.random()); return ResultWrapper.SUCCESS(user); } @ApiOperation("查询公钥") @PostMapping("/queryPublicKey") public ResultWrapper<String> getPublicKey(){ return ResultWrapper.SUCCESS(RSAUtils.getPublicKey().replaceAll("[\\s\t\n\r]", "")); } @ApiOperation("查询私钥") @PostMapping("/queryPrivateKey") public ResultWrapper<String> queryPrivateKey(){ return ResultWrapper.SUCCESS(RSAUtils.getPrivateKey().replaceAll("[\\s\t\n\r]", "")); } @ApiOperation("验签") @PostMapping("/check") public ResultWrapper<Boolean> check(@RequestBody CheckInfo checkInfo) throws Exception { boolean a = RSAUtils.verify(checkInfo.getData().getBytes(),RSAUtils.getPublicKey(), checkInfo.getSign()); return ResultWrapper.SUCCESS(a); } @ApiOperation("公钥解密") @PostMapping("/depByPublicKey") public ResultWrapper<String> depByPublicKey(@RequestBody CheckInfo checkInfo) throws Exception { return ResultWrapper.SUCCESS(Base64.encodeBase64String(RSAUtils.decryptByPublicKey(Base64.decodeBase64(checkInfo.getSign()),RSAUtils.getPublicKey()))); }}

f. 接口入参加解密测试:

要求接口:localhost:8082/user3/save

要求入参:

先将接口入参用客户端随机天生的AES密钥进行加密:

RSA公钥是:`MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArDDTNxnkM6gp7hhZm1d/hCcqMgeLvM2P7fuRWEHNsyl6dGL50hFlaMZnY8C4FVCR4XvgEgPguNDaKb8ID2C5+bgdnK0mMhJYX+fQmGbqzIycirQeHGXX5Hkgwvgp36GIn4vYNQSQMPks2WSUIkZIYtePXEd8zxoavM5f+HmmFH+y6/pMN5vikWa+mHbJLfA+wCA5hT37z0OyOQW5rTFCaTlZ5C3CCmTTB/IqkpmXybPR3HDBss77eHk/84EV1AkzfrtksYUIY0uGylpCuT1+GsXsvtaed876GNRT8PBAos1nSlQAcTEXDbZbdYMWrfkWvBlmD5sDzp4XDw5Uet9Q8QIDAQAB`

AES密钥明文是: `nl4tNpb0x11g58yx`

AES密钥明文通过RSA公钥加密后是:`PGyZr37ZedQ2nFbX++dwX+9TW5+SJKy3uo4AdYZwItXTgFqtXgCW22cNwp4rLluMZ1Psw+pgjQgN7M+SQapFfyoUhH9BFl/KEuLE3QRq/kFFawPudlGPDb8m4Zvf5G6X9X3CG98n1bA5D9wZKfzpgbISUhkFjO547JORTje3nUp6USFI/y35AcxG5t0g40ewzYnX7+WzW8NZtHOeyAnhINHQaSbL9Mze+YHOurEN3FJZbbrR4TxlYFRVEnVXWC6WISBigeVEWDgj1wsIDGuct22IkZkg4IM6sDIGWzfHFfZDX9ALF1EWyTzkT04f7UHv/o1gaNtz1N3multwP1kdrQ==`

须要将上面的加密结果放入要求头中,字段名是:aesKey

//原始业务数据入参{"id": 1,"username": "张三0.7966584801346173"}//AES加密后入参88Rvw/q7Pvs6ZHacR9KdjhXUz2Ja8TuYMWZRbVgA5CxXJEQSnsOjYXlPi67aRxJS

要求出参:

{"status": 200,"aesKey": null,"sign": null,"data": {"id": 1,"username": "张三0.7966584801346173"},"message": null}

h. 接口出参加密测试:

要求接口:localhost:8082/user3/query

要求入参:无

要求出参:

{ "status": 200, "aesKey": "bmw0dE5wYjB4MTFnNTh5eA==", "sign": "Sim2+YRqPCKSjDAhqRRcmMQE2bREj90K9k52qq8NOS8XfCvEOBW/+pMgD4YPRwZPYYM8vmbSW+n3\r\ndv9gNI1PU7Xz1zxVeLvgMT9DO4CDPUTGkHP8z7hEUchJfKBaqBXvaX6zfe89oqtk37YJ6rjU9fZn\r\nGWl427GoJJ9ysEmcGTxtKSa+31O3VHdwtvUDi9dBsXNojrDXeJ76Pbj5bZb2JbzoRcfl38HVfLeQ\r\nQtUgoaxyQ5KtCzCA00FcrJUg0IP19v5QGCYnlapRhOeQ95eARRUXavbs1ddmmMGZbfRFyIg3mN77\r\nInUwKYQhterJREiNNuQXdVYh/DPOr9lThmdb2A==", "data": "88Rvw/q7Pvs6ZHacR9KdjhXUz2Ja8TuYMWZRbVgA5CxXJEQSnsOjYXlPi67aRxJS", "message": null}

对要求出参中的aesKey在线工具进行解码,然后用解码后的aesKey、RSA公钥一起对署名sign进行合法性验证。

署名验证通过后利用aesKey对data数据进行解密即可得到终极业务数据。

2、文件加解密

小郭的思路1:读取原始文件的字节数据,通过AES加密算法天生新的加密文件,然后可以通过AES密钥解密得到原始文件。

小郭的思路2:读取原始文件的字节数据,通过AES加密算法天生新的加密文件,通过RSA公钥对加密文件数据进行署名,通过RSA私钥对署名进行验证(防修改),末了可以通过AES密钥解密得到原始文件。

小郭的思路3:读取原始文件的字节数据,通过RSA公钥天生新的加密文件,然后可以通过RSA私钥解密得到原始文件。

下面以思路1的实现来举例:

思路2、思路3的代码实现和前面提到的接口出入参加解密办法类似,网上也有很多类似的例子。
有兴趣的小伙伴可以自行实现下。

package com.gyd.encrypt.file;import org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.crypto.Cipher;import javax.crypto.CipherInputStream;import javax.crypto.CipherOutputStream;import javax.crypto.spec.SecretKeySpec;import java.io.;import java.security.SecureRandom;import java.util.Random;/ @ClassName AesFileUtils @Description 文件加解密AES算法实现 @Author guoyading @Date 2023/11/27 17:04 @Version 1.0 /public class AesFileUtils { / key的长度,Wrong key size: must be equal to 128, 192 or 256 传入时须要16、24、36 / private static final Integer KEY_LENGTH = 16 8; / 不能在代码中创建 JceSecurity.getVerificationResult 会将其put进 private static final Map<Provider,Object>中,导致内存缓便被耗尽 / private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider(); / 加密算法AES / private static final String KEY_ALGORITHM = "AES"; / 算法名称/加密模式/数据添补办法 默认:AES/ECB/PKCS5Padding / private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; / 后端AES的key,由静态代码块赋值 / public static String key; static { key = getKey(); } / 获取key / public static String getKey() { StringBuilder uid = new StringBuilder(); //产生16位的强随机数 Random rd = new SecureRandom(); for (int i = 0; i < KEY_LENGTH / 8; i++) { //产生0-2的3位随机数 int type = rd.nextInt(3); switch (type) { case 0: //0-9的随机数 uid.append(rd.nextInt(10)); break; case 1: //ASCII在65-90之间为大写,获取大写随机 uid.append((char) (rd.nextInt(25) + 65)); break; case 2: //ASCII在97-122之间为小写,获取小写随机 uid.append((char) (rd.nextInt(25) + 97)); break; default: break; } } return uid.toString(); } public static void aesEncryptFileForOutput(String sourceFilePath, String destFilePath) throws Exception { aesFileForOutput(sourceFilePath, destFilePath, key, Cipher.ENCRYPT_MODE); } public static void aesDecryptFileForOutput(String sourceFilePath, String destFilePath) throws Exception { aesFileForOutput(sourceFilePath, destFilePath, key, Cipher.DECRYPT_MODE); } public static void aesEncryptFileForInput(String sourceFilePath, String destFilePath) throws Exception { aesFileForInput(sourceFilePath, destFilePath, key, Cipher.ENCRYPT_MODE); } public static void aesDecryptFileForInput(String sourceFilePath, String destFilePath) throws Exception { aesFileForInput(sourceFilePath, destFilePath, key, Cipher.DECRYPT_MODE); } / 通过文件输出流加密文件并输出到指定路径 CipherOutputStream 进行加密数据 将源文件加密天生加密的新文件 sourceFilePath 原始文件路径 destFilePath加密文件路径 key 加密密钥 / public static void aesFileForOutput(String sourceFilePath, String destFilePath, String key, int mode) throws Exception { File sourceFile = new File(sourceFilePath); File destFile = new File(destFilePath); if (!(sourceFile.exists() && sourceFile.isFile())) { throw new IllegalArgumentException("加密源文件不存在"); } if (!destFile.exists()) { destFile.createNewFile(); } InputStream in = new FileInputStream(sourceFile); OutputStream out = new FileOutputStream(destFile); SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM,PROVIDER); cipher.init(mode, secretKeySpec); // 对输出流包装 CipherOutputStream cout = new CipherOutputStream(out, cipher); byte[] cache = new byte[1024]; int nRead = 0; while ((nRead = in.read(cache)) != -1) { cout.write(cache, 0, nRead); cout.flush(); } cout.close(); out.close(); in.close(); } / 通过文件输入流加密文件并输出到指定路径 CipherInputStream进行加密数据 将源文件加密天生加密的新文件 sourceFilePath 原始文件路径 destFilePath加密文件路径 key 加密密钥 / public static void aesFileForInput(String sourceFilePath, String destFilePath, String key, int mode) throws Exception { File sourceFile = new File(sourceFilePath); File destFile = new File(destFilePath); if (!(sourceFile.exists() && sourceFile.isFile())) { throw new IllegalArgumentException("加密源文件不存在"); } if (!destFile.exists()) { destFile.createNewFile(); } InputStream in = new FileInputStream(sourceFile); OutputStream out = new FileOutputStream(destFile); SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM,PROVIDER); cipher.init(mode, secretKeySpec); // 对输入流包装 CipherInputStream cin = new CipherInputStream(in, cipher); byte[] cache = new byte[1024]; int nRead = 0; while ((nRead = cin.read(cache)) != -1) { out.write(cache, 0, nRead); out.flush(); } out.close(); cin.close(); in.close(); } / AES加密方法,此处利用AES-128-ECB加密模式,key须要为16位 @param sKey @return / public static void encryptStream(ByteArrayOutputStream source, OutputStream out, String sKey) { long start = System.currentTimeMillis(); try { // 判断Key是否精确 if (sKey == null || sKey.length() != 16) { throw new RuntimeException("Key为空或长度不为16位"); } byte[] raw = sKey.getBytes(); SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM,PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); CipherOutputStream cout = new CipherOutputStream(out, cipher); cout.write(source.toByteArray()); cout.close(); source.close(); } catch (Exception e) { System.out.println("AES加密失落败:"+e); } } / AES解密方法,此处利用AES-128-ECB加密模式,key须要为16位 @param sKey @return / public static void decryptStream(InputStream in, OutputStream out, String sKey) { long start = System.currentTimeMillis(); try { // 判断Key是否精确 if (sKey == null || sKey.length() != 16) { throw new RuntimeException("Key为空或长度不为16位"); } byte[] raw = sKey.getBytes("utf-8"); SecretKeySpec skeySpec = new SecretKeySpec(raw, KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_ALGORITHM,PROVIDER); cipher.init(Cipher.DECRYPT_MODE, skeySpec); // 先用base64解密 long time = (System.currentTimeMillis() - start); System.out.println("AES解密耗时:"+time+" ms"); CipherOutputStream cout = new CipherOutputStream(out, cipher); byte[] cache = new byte[1024]; int nRead = 0; while ((nRead = in.read(cache)) != -1) { cout.write(cache, 0, nRead); cout.flush(); } cout.close(); in.close(); } catch (Exception e) { System.out.println("AES解密失落败:"+e); } }}

package com.gyd.encrypt.demo;import com.gyd.encrypt.file.AesFileUtils;/ @ClassName FileDemO @Description 文件加解密demo(AES实现) @Author guoyading @Date 2023/11/27 17:04 @Version 1.0 /public class FileDemo { public static void main(String[] args) throws Exception { //通过文件输出流加密办法,实现文件加密和解密 AesFileUtils.aesEncryptFileForOutput("D:\\workspace\\source.xlsx","D:\\workspace\\111.xxxx"); AesFileUtils.aesDecryptFileForOutput("D:\\workspace\\111.xxxx","D:\\workspace\\dest1.xlsx"); //通过文件输入流加密办法,实现文件加密和解密 AesFileUtils. aesEncryptFileForInput("D:\\workspace\\source.xlsx","D:\\workspace\\222.xxxx"); AesFileUtils.aesDecryptFileForInput("D:\\workspace\\222.xxxx","D:\\workspace\\dest2.xlsx"); }}3、跨措辞调用加解密

跨措辞接口调用和措辞内部接口调用的加解密流程实在是很相似的,唯不同措辞的开拓者之间只须要约定好加密算法和对应加解密的密钥交流办法即可。
唯一不同的是不同措辞对同一种加密算法的组件库(library库)不同。

标签:

相关文章

语言枚举类型,探索人类语言多样性的奥秘

语言是人类交流的重要工具,也是人类文明发展的重要标志。随着全球化进程的不断推进,各种语言枚举类型应运而生。本文将从语言枚举类型的定...

SEO优化 2024-12-29 阅读0 评论0

语言栏消失,科技变革下的挑战与机遇

近年来,随着科技的飞速发展,智能手机、平板电脑等移动设备的普及,语言栏这一功能已经成为了我们日常生活中不可或缺的一部分。近期有消息...

SEO优化 2024-12-29 阅读0 评论0

语言混合现象的多元魅力与挑战

语言混合作为一种跨文化交流的现象,逐渐成为世界范围内语言学研究的热点。它不仅丰富了语言的多样性,也反映了全球化背景下人类社会的交流...

SEO优化 2024-12-29 阅读0 评论0

语言是思想的载体,介绍语言与思想的关系

在人类文明的进程中,语言一直扮演着至关重要的角色。它不仅是人们沟通交流的工具,更是承载着人类思想的载体。自古以来,人们就深知语言与...

SEO优化 2024-12-29 阅读0 评论0