Jakob Jenkov 个人博客 JCE 部分(译文)

Java Cryptography API 使你能够在 Java 中加密和解密数据,以及管理密钥、签名、验证消息、计算加密哈希等等。Cryptography 属于经常被简称为 crypto,所以你有时候可能会看到 Java crypto 而不是 Java Cryptography,这两个术语是同一个意思。

Java Cryptography API 由被称为 Java Cryptography Extension 提供,Java Cryptography Extension 经常被引用通过它的缩写 JCE。Java Cryptography Extension 长期以来一直是 Java 平台的一部分。JCE 最初与 Java 是分开的,因为美国对加密技术有一些出口限制。因此,最强的加密算法并未包含在标准 Java 平台中。如果你是美国境内的公司,你可以从 Java JCE 获得这些更强大的加密算法,但世界其他地区不得不使用较弱的算法。到 2017 年的时候,美国加密出口规则已经放宽了很多,因此,世界上大部分地区都可以通过 JCT 从国际加密标准中受益。

Java Cryptography Architecture(JCA)是 Java 加密 API 内部设计的名称。JCA 是围绕一些核心通用类和接口构建的。这些接口背后的真正功能是由 Providers 提供的。因此,你可以使用一个 Cipher 类来加密和解密某些数据,但具体的密码实现取决于所使用的具体 Provider。

Provider

java.security.Provider 类是 Java cryptography API 中的核心类。为了使用 Java crypto API,你需要一组 Provider。Java SDK 带有自己的 Provider。如果你没有显示设置 Provider,则会使用 Java SDK 默认的 Provider。但是此 Provider 可能不支持你要使用的加密算法。因此,你可能不得不设置自己的 Provider。

在 Java cryptography API 中最流行的 Provider 之一是 Bouncy Castle,如下是一个设置 BouncyCastleProvider 的示例:

import org.bouncycastle.jce.provider.BouncyCastleProvider;import java.security.Security;public class ProviderExample {public static void main(String[] args) {Security.addProvider(new BouncyCastleProvider());}
}

Cipher

javax.crypto.Cipher 类表示一种加密算法,Cipher 是在密码学领域中一个加密算法的标准术语,这就是为什么 Java 类被叫做 Cipher 而不是 Encrypter/Decrypter 或者其他的原因。

你可以使用一个 Cipher 实例在 Java 中加密和解密数据。

创建 Cipher

在你使用 Cipher 之前需要先创建一个 Cipher 类的实例。你可以通过调用 Cipher 的 getInstance() 方法来创建一个 Cipher 实例,该方法需要传入一个用来表明你想要使用的加密算法的参数,实例如下:

Cipher cipher = Cipher.getInstance("AES");

此示例创建了一个使用 AES 加密算法的 Cipher 实例。

Cipher 模式

一些加密算法可以在不同的模式下工作,加密模式指定有关算法应如何加密数据的详细信息,因此,加密模式影响了部分加密算法。

加密模式有时可以与多种不同的加密算法一起使用 ---- 就像附加到核心加密算法的一门技术。这就是为什么这些模式被认为与加密算法本身是分开的,而不是具体加密算法的 “附加组件”。以下是一些著名的 cipher 模式:

  • ECB - Electronic Codebook
  • CBC - Cipher Block Chaining
  • CFB - Cipher Feedback
  • OFB - Output Feedback
  • CTR - Counter

初始化 Cipher 时,你可以将其模式附加到加密算法的名称后面。例如,要使用 Cipher Block Chaining(CBC)创建 AES Cipher 实例,你可以使用以下代码:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

由于密码块链接也需要 “填充方案”,因此 “填充方案” 附加在加密算法名称字符串的末尾。

值得注意的是,默认的 Java SDK Provider 并不包含所有的加密算法和模式,你可能需要一个外部的 Provider 比如 Bouncy Castle 来使用你想要的模式和填充方案来实例化 Cipher。

初始化 Cipher

在使用 Cipher 实例之前,你必须对其进行初始化。初始化一个 Cipher 是指调用它的 init() 方法,init() 方法需要两个参数:

  • 加密/解密密码操作模式
  • 加密/解密密钥

下面是一个以加密方式初始化 Cipher 实例的例子:

Key key = ... // get / create symmetric encryption key
cipher.init(Cipher.ENCRYPT_MODE, key);

下面是一个以解密方式初始化 Cipher 实例的例子:

Key key = ... // get / create symmetric encryption key
cipher.init(Cipher.DECRYPT_MODE, key);

加密和解密数据

为了使用 Cipher 实例加密或解密数据,你可以调用以下两个方法中的一个:

  • update()
  • doFinal()

这两个方法都有多个接受不同参数的重载版本,我将在这里介绍最常用的版本。

如果你想要加密/解密单个数据块,只需调用 doFinal() 并传入需要加密/解密的数据。如下是一个加密示例:

byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); 
byte[] cipherText = cipher.doFinal(plainText);

只需要在初始化 Cipher 的时候指定是加密模式还是解密模式,就可以使用同一个方法 doFinal 来加密/解密数据。

如果你想要加密/解密多个数据块,例如来自一个大文件的多个块,你可以为每个数据块调用一次 update(),并在最后一个数据块调用 doFinal(),下面是一个加密多个数据块的例子:

byte[] data1 = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); 
byte[] data2 = "zyxwvutsrqponmlkjihgfedcba".getBytes("UTF-8"); 
byte[] data3 = "01234567890123456789012345".getBytes("UTF-8"); byte[] cipherText1 = cipher.update(data1); 
byte[] cipherText2 = cipher.update(data2); 
byte[] cipherText3 = cipher.doFinal(data3);

最后一个数据块需要调用 doFinal() 的原因是,一些加密算法需要填充数据以适应确定的密码块大小(比如 8 byte 边界)。但是我们不想填充中间的数据块,因此中间数据块调用 update(),最后一个数据块调用 doFinal()

当解密多个数据块时也是一样的,以下是使用 Cipher 实例解密多个数据块的示例:

byte[] plainText1 = cipher.update(cipherText1); 
byte[] plainText2 = cipher.update(cipherText2); 
byte[] plainText3 = cipher.doFinal(cipherText3);

在这之前记得将 Cipher 实例初始化为解密模式。

加密/解密部分字节数组

Cipher 类的加解密方法能够加密/解密一个字节数组中的部分数据,你只需要简单地将 offset 和 length 传递给 update() 或者 doFinal() 方法即可,如下所示:

int offset = 10;
int length = 24;
byte[] cipherText = cipher.doFinal(data, offset, length);

此示例加密从索引 8 开始的 24 个字节。

加密/解密到现有字节数组中

到目前为止,本教程中展示的所有加密和解密示例都是通过返回一个新的字节数组,然而,你也可以将加密/解密后的数据填充到现有的字节数组中,这对于减少创建的字节数组的数量很有用。

通过将目标字节数组作为参数传递给 update()doFinal() 方法来可以实现这一点,示例如下:

int offset = 10;
int length = 24;
byte[] dest = new byte[1024];
cipher.doFinal(data, offset, length, dest);

此示例加密源字节数组中从索引 10 开始的 24 个字节,并将加密后的数据从索引 0 开始填充到 dest 字节数组中,如果你想要为 dest 字节数组设置不同的起始索引,Cipher 类中有额外接收目标字节数组偏移量的版本的 update()doFinal() 方法,下面示例中给 doFinal() 方法传递了目标字节数组的偏移量:

int offset = 10;
int length = 24;
byte[] dest = new byte[1024];
int destOffset = 12
cipher.doFinal(data, offset, length, dest, destOffset);

重用 Cipher 实例

初始化 Cipher 实例是一项开销很大的操作。因此,重用 Cipher 实例是个好主意。幸运的是,Cipher 类在设计时考虑到了重用。

当你在 Cipher 实例上调用 doFinal() 方法时,Cipher 实例将返回到初始化后的状态。Cipher 实例能够用来加密解密数据多次。

下面是一个重用 Cipher 实例的例子:

Cipher cipher = Cipher.getInstance("AES");Key key = ... // get / create symmetric encryption key
cipher.init(Cipher.ENCRYPT_MODE, key);byte[] data1 = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
byte[] data2 = "zyxwvutsrqponmlkjihgfedcba".getBytes("UTF-8");byte[] cipherText1 = cipher.update(data1);
byte[] cipherText2 = cipher.doFinal(data2);byte[] data3 = "01234567890123456789012345".getBytes("UTF-8");
byte[] cipherText3 = cipher.doFinal(data3);

MessageDigest

Java MessageDigest 类表示一个加密散列算法,它被用来从二进制数据中计算消息摘要。当你收到一些加密数据时,你无法从数据本身看出它是否在传输过程中被修改,消息摘要可以帮助解决该问题。

为了能够检测加密数据是否在传输过程中被修改,发送方可以根据数据计算消息摘要并将其与数据一起发送。当你收到加密后的数据和消息摘要时,你可以根据数据重新计算消息摘要,并检查计算出的消息摘要是否与随数据接收的消息摘要相匹配。如果两个消息摘要匹配,则有可能加密数据在传输过程中未被修改。

创建 MessageDigest 实例

要创建 MessageDigest 实例,你可以调用 MessageDigest 类的静态方法 getInstance()。以下是创建 MessageDigest 实例的示例:

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");

传递给 getInstance() 方法的文本参数是要使用的具体消息摘要算法的名称。

消息摘要算法

Java Cryptography API 支持以下消息摘要算法:

  • MD2
  • MD5
  • SHA-1
  • SHA-256
  • SHA-384
  • SHA-512

并非所有的这些消息摘要算法都同样安全,建议使用 SHA-256 或更高版本以获得尽可能高的安全性。

计算 MessageDigest

一旦你创建了 MessageDigest 实例之后,你就可以使用它来计算数据的消息摘要了,如果你有单个数据块来计算消息摘要,请使用 digest() 方法,以下是从单个数据块计算消息摘要的方式:

byte[] data1 = "0123456789".getBytes("UTF-8"); MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 
byte[] digest = messageDigest.digest(data1);

如果你有多个数据块要包含在同一个消息摘要中,请先调用 update() 方法并在最后调用 digest() 方法,以下是从多个数据块计算消息摘要的方式:

byte[] data1 = "0123456789".getBytes("UTF-8");
byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(data1);
messageDigest.update(data2);byte[] digest = messageDigest.digest();

Mac

javax.crypto.Mac 类可以从二进制数据中创建 Message Authentication Code,一个 Mac 是一个使用安全密钥加密之后的消息摘要,只有当你有用密钥时,才能验证 MAC。

创建 Mac 实例

在你使用 Mac 类之前你必须先创建 Mac 实例,创建 Mac 实例可以使用 getInstance() 静态方法,示例如下:

Mac mac = Mac.getInstance("HmacSHA256");

传递给 getInstance() 方法的字符串参数包含了要使用的 MAC 算法的名字,在这个例子中是 HmacSHA256。

初始化 Mac

在创建 Mac 实例之后,你需要初始化,初始化 Mac 实例是通过调用 init() 方法并传入安全密钥作为参数,如下所示:

byte[] keyBytes   = new byte[]{0,1,2,3,4,5,6,7,8 ,9,10,11,12,13,14,15};
String algorithm  = "RawBytes";
SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm);mac.init(key);

init() 方法接受一个 Key 实例,在这个例子中是 SecreKeySpec,它是 Key 接口的实现类。

计算 Mac

初始化之后你就可以计算 Mac 的值了,计算 Mac 的值你需要调用 update() 或者 doFinal() 方法,如果你只有一个数据块需要计算,你可以直接使用 doFinal() 方法,如下所示:

byte[] data  = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
byte[] macBytes = mac.doFinal(data);

如果你有多个数据块要计算,那么你必须给每个数据块调用 update() 方法,并在最后调用 doFinal() 方法,示例如下:

byte[] data  = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
byte[] data2 = "0123456789".getBytes("UTF-8");mac.update(data);
mac.update(data2);byte[] macBytes = mac.doFinal();

Signature

java.security.Signature 类可以为二进制数据创建数字签名,数字签名是使用私钥/公钥对中的私钥加密后的消息摘要。任何拥有公钥的人都可以验证该数字签名。

创建 Signature 实例

在使用 Signature 类之前,你必须创建一个 Signature 实例,你可以通过调用静态方法 getInstance() 方法创建一个 Signature 实例,下面是一个创建 Signature 实例的示例:

Signature signature = Signature.getInstance("SHA256WithDSA");

作为参数传递给 getInstance() 方法的字符串是要使用的数字签名算法的名称。

初始化 Signature 实例

创建 Signature 实例后,你需要先对其进行初始化,然后才能使用它。你通过调用 Signature 实例的 init() 方法来初始化实例,这是一个 Signature 初始化示例:

SecureRandom secureRandom = new SecureRandom();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();signature.initSign(keyPair.getPrivate(), secureRandom);

如你所见,Signature 实例是使用私钥/公钥对的私钥和一个 SecureRandom 实例初始化的。

创建数字签名

Signature 实例被初始化后,你就可以使用它来创建数字签名。你可以通过调用一次或多次 update() 方法来创建数字签名,并在最后调用 sign() 方法,以下是为二进制数据块创建数字签名的示例:

byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
signature.update(data);byte[] digitalSignature = signature.sign();

验证数字签名

如果要验证其他人创建的数字签名,则必须将 Signature 实例初始化为验证模式而不是签名模式。以下是将 Signature 实例初始化为验证模式的方式:

Signature signature = Signature.getInstance("SHA256WithDSA");signature.initVerify(keyPair.getPublic());

在上面的示例中,Signature 实例被初始化为验证模式,并将公钥/私钥对的公钥作为参数传递进去了。

一旦初始化为验证模式,你就可以使用 Signature 实例来验证一个数字签名了,如下示例展示了如何验证一个数字签名:

byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
signature2.update(data2);boolean verified = signature2.verify(digitalSignature);

完整的签名和验证示例如下:

SecureRandom secureRandom = new SecureRandom();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
KeyPair keyPair = keyPairGenerator.generateKeyPair();Signature signature = Signature.getInstance("SHA256WithDSA");signature.initSign(keyPair.getPrivate(), secureRandom);byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8");
signature.update(data);byte[] digitalSignature = signature.sign();Signature signature2 = Signature.getInstance("SHA256WithDSA");
signature2.initVerify(keyPair.getPublic());signature2.update(data);boolean verified = signature2.verify(digitalSignature);System.out.println("verified = " + verified);

KeyGenerator

javax.crypto.KeyGenerator 被用于生成对称加密密钥,对称加密密钥是一种密钥,对称加密算法可以使用它来加密和解密数据。

创建 KeyGenerator 实例

在使用 KeyGenerator 类之前,你必须先创建一个 KeyGenerator 实例,你可以通过调用静态方法 getInstance() 并传递创建密钥的加密算法的名称来做到这一点,以下是创建 KeyGenerator 实例的示例:

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

此示例创建一个 KeyGenerator 实例,该实例可以通过 AES 加密算法生成密钥。

初始化 KeyGenerator

创建 KeyGenerator 实例后,你必须对其进行初始化,初始化一个 KeyGenerator 实例是通过调用它的 init() 方法来完成的,这是初始化 KeyGenerator 实例的示例:

SecureRandom secureRandom = new SecureRandom();
int keyBitSize = 256;keyGenerator.init(keyBitSize, secureRandom);

KeyGenerator 的 init() 方法接收两个参数:要生成的密钥的 bit 位是多少,以及一个 SecureRandom 实例。

生成密钥

初始化 KeyGenerator 实例后,你可以使用它来生成密钥。生成密钥是通过调用 KeyGenerator.generateKey() 方法完成的。以下是生成对称密钥的示例:

SecretKey secretKey = keyGenerator.generateKey();

KeyPair

java.security.KeyPair 代表一个非对称密钥对,换句话说,就是一个公钥私钥对,KeyPair 实例通常在执行非对称加密时使用,例如对数据进行加密或者签名的时候。

获取 KeyPair 实例

你通常会从 Java KeyStore 或 Java KeyPairGenerator 获取一个 KeyPair 实例。

访问 KeyPair 中的 Public Key

你可以通过调用 KeyPair.getPublic() 方法来访问一个 PulicKey,实例如下:

PublicKey publicKey = keyPair.getPublic();

访问 KeyPair 中的 Private Key

你可以通过调用 KeyPair 的 getPrivate() 方法来访问 PrivateKey,示例如下:

PrivateKey privateKey = keyPair.getPrivate();

KeyPairGenerator

java.security.KeyPairGenerator 类用于生成非对称加密/解密密钥对。非对称密钥对由两个密钥组成。第一个密钥通常用于加密数据,第二个密钥用于解密第一个密钥加密的数据。

最广为人知的非对称密钥对类型是公钥、私钥类型的密钥对。私钥用于加密数据,公钥用于解密数据。实际上,你也可以使用公钥加密数据并使用私钥解密。

私钥通常是保密的,公钥通常是公开的。

创建 KeyPairGenerator 实例

要使用 KeyPairGenerator,你必须首先创建一个 KeyPairGenerator 实例,创建 KeyPairGenerator 实例是通过调用 getInstance() 方法完成的,以下是创建 KeyPairGenerator 实例的示例:

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

getInstance() 方法通过接收加密算法的名称作为参数来生成 KeyPairGenerator,在此例中,我们使用 RSA 加密算法。

初始化 KeyPairGenerator

根据生成密钥对的算法,你可能必须初始化 KeyPairGenerator 实例,初始化 KeyPairGenerator 是通过调用它的 initialize() 方法来完成的,下面是初始化一个 KeyPairGenerator 实例的例子:

keyPairGenerator.initialize(2048);

此示例初始化 KeyPairGenerator 以生成大小为 2048 位的密钥。

生成 KeyPair

要使用一个 KeyPairGenerator 生成一个 KeyPair,你可以调用 generatorKeyPair() 方法,下面是一个生成 KeyPair 的示例:

KeyPair keyPair = keyPairGenerator.generateKeyPair();

KeyStore

Java KeyStore 是一个可以存储密钥的数据库,一个 Java KeyStore 在 Java 中的抽象是 java.security.KeyStore 类。一个 KeyStore 可以被写入磁盘并再次读取,KeyStore 作为一个整体可以用密钥保护,并且 KeyStore 中的每个 key entry 可以有自己的密码来保护自己。这使得 KeyStore 成为安全处理加密密钥的有用机制。

一个 KeyStore 可以持有以下类型的密钥:

  • Private keys
  • Public keys + certificates
  • Secret keys

私钥和公钥用于非对称加密,公钥可以有关联的证书,证书是一个用于验证持有公钥的个人、组织和设备身份的文档。证书通常由验证方进行数字签名作为证明。

安全密钥用于非对称加密,在大部分场景中,在一个安全连接被建立的时候会商定非对称密钥,因此你可能使用 KeyStore 存储公钥和私钥的时间存储安全密钥的时间多。

创建 KeyStore

你可以通过调用 KeyStore.getInstance() 方法来创建 KeyStore,以下是创建 KeyStore 的示例:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());

此示例创建默认类型的 KeyStore 实例,也可以通过将不同的参数传递给 getInstance() 方法来创建其他类型的实例,例如,这是一个创建 PKCS12 类型的示例:

KeyStore keyStore = KeyStore.getInstance("PKCS12");

加载 KeyStore

在 KeyStore 实例可以使用之前,必须先加载它。KeyStore 实例通常被写入磁盘或其他类型的存储以备后用。这就是为什么 KeyStore 假定你必须先读入它的数据才能使用它的原因。但是,可以初始化一个没有数据的空 KeyStore 实例。

从文件或其他存储中加载 KeyStore 数据是通过调用 KeyStore.load() 方法来完成的,该方法有两个参数:

  1. 加载数据的 InputStream
  2. 包含 KeyStore 密码的 char[]

这是加载 KeyStore 的示例:

char[] keyStorePassword = "123abc".toCharArray(); 
try(InputStream keyStoreData = new FileInputStream("keystore.ks")){ keyStore.load(keyStoreData, keyStorePassword); 
}

此示例加载位于 keystore.ks 文件中的 KeyStore。

如果你不想将任何数据加载到 KeyStore 中,只需给 InputStream 参数传递 null 即可,下例示例加载一个空的 KeyStore:

keyStore3.load(null, keyStorePassword);

你必须始终加载 KeyStore 实例,无论是有数据的 KeyStore 还是空的 KeyStore,否则 KeyStore 是未初始化的,所有对其方法的调用都将抛出异常。

获取 Keys

你可以通过调用 getEntry() 方法来获取 KeyStore 中的密钥,一个 KeyStore Entry 映射一个密钥的别名,每个密钥被自己的密钥保护。因此要访问密钥,你必须将密钥的别名和密钥的密码传递给 getEntry() 方法,下面是访问 KeyStore 实例中的 key entry 的示例:

char[] keyPassword = "789xyz".toCharArray(); 
KeyStore.ProtectionParameter entryPassword = new KeyStore.PasswordProtection(keyPassword); KeyStore.Entry keyEntry = keyStore3.getEntry("keyAlias", entryPassword);

如果你知道要访问的密钥是一个私钥,则可以将 KeyStore.Entry 实例转换为 KeyStore.PrivateKeyEntry,示例如下:

KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore3.getEntry("keyAlias", entryPassword);

转换成 KeyStore.PrivateKeyEntry 之后,通过以下方法你能够访问私钥,证书和证书链。

  • getPrivateKey()
  • getCertificate()
  • getCertificateChain()

设置 Keys

你也可以给 KeyStore 实例设置密钥,示例如下:

SecretKey secretKey = getSecretKey();
KeyStore.SecretKeyEntry secretKeyEntry = new KeyStore.SecretKeyEntry(secretKey);keyStore3.setEntry("keyAlias2", secretKeyEntry, entryPassword);

存储 KeyStore

有时,你可能想要存储一个 KeyStore 到存储器中(磁盘、数据库等),用于在某个时间再次加载它。你可以通过 store() 方法存储一个 KeyStore,示例如下:

char[] keyStorePassword = "123abc".toCharArray();
try (FileOutputStream keyStoreOutputStream = new FileOutputStream("data/keystore.ks")) {keyStore3.store(keyStoreOutputStream, keyStorePassword);
}

参考:
https://jenkov.com/tutorials/java-cryptography/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/98987.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OSI七层网络模型

1. OSI模型 网络协议是网络中两台计算机之间传输数据的标准语言。各种计算机系统使用 OSI(Open Systems Interconnection)模型规定的标准相互通信。OSI 模型有七个抽象层,每个层都有不同的职责和协议。 下图显示了 OSI 模型中每一层的功能。…

【python海洋专题十四】读取多个盐度nc数据画盐度季节变化图

本期内容 读取多个盐度文件;拼接数据在画盐度的季节分布图Part01. 使用数据 IAP 网格盐度数据集 数据详细介绍: 见文件附件: pages/file/dl?fid378649712527544320 全球温盐格点数据.pdf IAP_Global_ocean_gridded_product.pdf 全球温…

Blender中的4种视图着色模式

Blender中有四种主要的视图着色模式:线框、实体、Look Dev和渲染。它们的主要区别如下: - 线框模式只显示物体的边缘(线框),可以让您看到场景中的所有物体,也可以调整线框的颜色和背景的颜色。 - 实…

python实现UI自动化配置谷歌浏览器驱动

web端UI自动化执行在哪个浏览器,需要对应哪个浏览器的驱动。以谷歌浏览器为例,进行配置。一、查看谷歌浏览器版本 如下截图:我的谷歌浏览器版本是: 117.0.5938.150 二、下载对应版本谷歌浏览器驱动 首先可以从其他版本驱动地址中…

【AI】深度学习——人工智能、深度学习与神经网络

文章目录 0.1 如何开发一个AI系统0.2 表示学习(特征处理)0.2.1 传统特征学习特征选择过滤式包裹式 L 1 L_1 L1​ 正则化 特征抽取监督的特征学习无监督的特征学习 特征工程作用 0.2.2 语义鸿沟0.2.3 表示方式关联 0.2.4 表示学习对比 0.3 深度学习0.3.1 表示学习与深度学习0.3.…

【数据库系统概论】数据定义之基本表的定义/创建、修改和删除

前言 🚩定义/创建基本表语法示例 修改基本表语法示例 删除基本表语法示例 感谢 💖 前言 🚩 SQL支持数据库系统的三级模式结构,其模式、外模式和内模式中的基本对象有表、视图和索引,因此,SQL的数据定义功能…

云原生Kubernetes:K8S集群kubectl命令汇总

目录 一、理论 1.概念 2. kubectl 帮助方法 3.kubectl 子命令使用分类 4.使用kubectl 命令的必要环境 5.kubectl 详细命令 一、理论 1.概念 kubectl是一个命令行工具,通过跟 K8S 集群的 API Server 通信,来执行集群的管理工作。 kubectl命令是操…

智慧楼宇3D数据可视化大屏交互展示实现了楼宇能源的高效、智能、精细化管控

智慧园区是指将物联网、大数据、人工智能等技术应用于传统建筑和基础设施,以实现对园区的全面监控、管理和服务的一种建筑形态。通过将园区内设备、设施和系统联网,实现数据的传输、共享和响应,提高园区的管理效率和运营效益,为居…

Maven系列:第1篇:什么是maven?为什么需要它?

maven系列目标:从入门开始开始掌握一个高级开发所需要的maven技能。 这是maven系列第1篇。 为什么我们要学习maven? 学习某些技术,肯定是我们遇到了某些问题,而这些问题目前手头上没有很好的方案去解决,此时刚好有一种技术可以…

增强现实抬头显示AR-HUD

增强现实抬头显示(AR-HUD)可以将当前车身状态、障碍物提醒等信息3D投影在前挡风玻璃上,并通过自研的AR-Creator算法,融合实际道路场景进行导航,使驾驶员无需低头即可了解车辆实时行驶状况。结合DMS系统,可以…

一个rar压缩包如何分成三个?

一个rar压缩包体积太大了,想要将压缩包分为三个,该如何做到?其实很简单,方法就在我们经常使用的WinRAR当中。 我们先将压缩包内的文件解压出来,然后查看一下,然后打开WinRAR软件,找到文件&…

7个在Github上的flutter开源程序

阅读大量代码是提高开发技能的最佳方法之一。该开源项目是了解最佳实践、编码风格和许多其他主题的最佳场所。 软件开发最受欢迎的领域之一是跨平台移动应用程序开发。Flutter 是您可以使用的最流行的跨平台移动应用程序开发工具之一。今天,我们将了解 7 个开源 Flu…

2023年中国烹饪机器人市场发展概况分析:整体规模较小,市场仍处于培育期[图]

烹饪机器人仍属于家用电器范畴,是烹饪小家电的进一步细分,它是烹饪小家电、人工智能和服务机器在厨房领域的融合。烹饪机器人是一种智能化厨房设备,可以根据预设的程序实现自动翻炒和烹饪,是多功能料理机和炒菜机结合的产物。 烹…

动画圆圈文字标志效果

效果展示 CSS 知识点 实现圆圈文字animation 属性回顾 实现思路 从效果的实现思路很简单,其实就是两个圆圈就可以实现。外层大圆(灰色)用于圆圈文字的展示,而内圆(藏青色)主要用于存放 Logo 图片。布局采…

OpenCV4(C++)—— 仿射变换、透射变换和极坐标变换

文章目录 一、仿射变换1. getRotationMatrix2D()2. warpAffine() 二、透射变换三、极坐标变换 一、仿射变换 在OpenCV中没有专门用于图像旋转的函数,而是通过图像的仿射变换实现图像的旋转。实现图像的旋转首先需要确定旋转角度和旋转中心,之后确定旋转…

c#设计模式-行为型模式 之 状态模式

🚀简介 状态模式是一种行为设计模式,它允许对象在其内部状态改变时改变其行为,我们可以通过创建一个状态接口和一些实现了该接口的状态类来实现状态模式。然后,我们可以创建一个上下文类,它会根据其当前的状态对象来改…

关联规则挖掘(下):数据分析 | 数据挖掘 | 十大算法之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…

CUDA+cuDNN+TensorRT 配置避坑指南

深度学习模型加速部署的环境配置,需要在本地安装NVIDIA的一些工具链和软件包,这是一个些许繁琐的过程,而且一步错,步步错。笔者将会根据自己的经验来提供建议,减少踩坑几率。当然可以完全按照官方教程操作,…

【Sentinel】Sentinel原码分析

本文内容来自【黑马】Sentinel从使用到源码解读笔记,做了部分修改和补充 目录 Sentinel 基本概念 基本流程 Node Entry 定义资源的两种方式 使用try-catch定义资源 使用注解标记资源 基于注解标记资源的实现原理 Context 什么是Context Context的初始化 …

SpringBoot青海省旅游系统

本系统采用基于JAVA语言实现、架构模式选择B/S架构,Tomcat7.0及以上作为运行服务器支持,基于JAVA、JSP等主要技术和框架设计,idea作为开发环境,数据库采用MYSQL5.7以上。 开发环境: JDK版本:JDK1.8 服务器…