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

2023-10-09 15:28

本文主要是介绍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

这篇关于Jakob Jenkov 个人博客 JCE 部分(译文)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/173788

相关文章

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

HomeBank:开源免费的个人财务管理软件

在个人财务管理领域,找到一个既免费又开源的解决方案并非易事。HomeBank&nbsp;正是这样一个项目,它不仅提供了强大的功能,还拥有一个活跃的社区,不断推动其发展和完善。 开源免费:HomeBank 是一个完全开源的项目,用户可以自由地使用、修改和分发。用户友好的界面:提供直观的图形用户界面,使得非技术用户也能轻松上手。数据导入支持:支持从 Quicken、Microsoft Money

分布式系统的个人理解小结

分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字

笔记整理—内核!启动!—kernel部分(2)从汇编阶段到start_kernel

kernel起始与ENTRY(stext),和uboot一样,都是从汇编阶段开始的,因为对于kernel而言,还没进行栈的维护,所以无法使用c语言。_HEAD定义了后面代码属于段名为.head .text的段。         内核起始部分代码被解压代码调用,前面关于uboot的文章中有提到过(eg:zImage)。uboot启动是无条件的,只要代码的位置对,上电就工作,kern

LeetCode 第414场周赛个人题解

目录 Q1. 将日期转换为二进制表示 原题链接 思路分析 AC代码 Q2. 范围内整数的最大得分 原题链接 思路分析 AC代码 Q3. 到达数组末尾的最大得分 原题链接 思路分析 AC代码 Q4. 吃掉所有兵需要的最多移动次数 原题链接 思路分析 AC代码 Q1. 将日期转换为二进制表示 原题链接 Q1. 将日期转换为二进制表示 思路分析

项目实战系列三: 家居购项目 第四部分

购物车 🌳购物车🍆显示购物车🍆更改商品数量🍆清空购物车&&删除商品 🌳生成订单 🌳购物车 需求分析 1.会员登陆后, 可以添加家居到购物车 2.完成购物车的设计和实现 3.每添加一个家居,购物车的数量+1, 并显示 程序框架图 1.新建src/com/zzw/furns/entity/CartItem.java, CartItem-家居项模型 /***

码蹄集部分题目(2024OJ赛9.4-9.8;线段树+树状数组)

1🐋🐋配对最小值(王者;树状数组) 时间限制:1秒 占用内存:64M 🐟题目思路 MT3065 配对最小值_哔哩哔哩_bilibili 🐟代码 #include<bits/stdc++.h> using namespace std;const int N=1e5+7;int a[N],b[N],c[N],n,q;struct QUERY{int l,r,id;}que

关于断言的部分用法

1、带变量的断言  systemVerilog assertion 中variable delay的使用,##[variable],带变量的延时(可变延时)_assertion中的延时-CSDN博客 2、until 的使用 systemVerilog assertion 中until的使用_verilog until-CSDN博客 3、throughout的使用   常用于断言和假设中的

牛客小白月赛100部分题解

比赛地址:牛客小白月赛100_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ A.ACM中的A题 #include<bits/stdc++.h>using namespace std;#define ll long long#define ull = unsigned long longvoid solve() {ll a,b,c;cin>>a>>b>