banner
leaf

leaf

It is better to manage the army than to manage the people. And the enemy.
follow
substack
tg_channel

Cryptography of Blockchain

The password hashing function is a purely deterministic function that maps input from a large space to a fixed set of outputs. These outputs are commonly referred to as input digests. For example, the input could be the entire text of the preface of this book, and its digest could be a hexadecimal value from a 128-bit value space.

In informal terms, a secure hashing function should be used. This means that it should be nearly impossible to find two different inputs that produce the same digest. Hash functions should also be irreversible. If only the digest is given, it should be impossible to find the input that produced the digest. Similarly, small changes to the input should produce significant changes in the output digest—two similar inputs should have very different digests. Hash functions should also be relatively fast to compute from their inputs, making it a simple task to verify that an input matches its digest.

Hash values are central to maintaining the integrity of the blockchain and are the basis of the proof-of-work consensus mechanism. We will see these two uses over several pages.

Public Key Cryptography

Public key cryptography can be implemented through many different algorithms, one of the most popular being Rivest-Shamir-Adleman (more commonly known as RSA), which relies on the Elliptic Curve Digital Signature Algorithm (ECDSA). This algorithm also allows for the recovery of the public key given a message and its signature.

With these two cryptographic concepts, we can finally begin learning how to build a blockchain.

A blockchain is a decentralized, immutable public digital ledger. We can think of a blockchain as a distributed database where once a record is confirmed, it can never be deleted or modified, and no authority can control this database, which is replicated across all nodes in a peer-to-peer network. The actual content stored in the database may vary; it can be currency, asset registrations, or even executable code.

In a blockchain, each state change is part of a transaction. A transaction can be viewed as an anatomical write operation from users in a global database that may change one or more arrays. Any user in the network can submit a transaction to be executed.

How the transfer is handled is part of the blockchain state transition relationship. By processing each transaction it receives, the blockchain transitions from one state to another. For example, a blockchain managing currency might handle the transfer of currency between two accounts: it decreases the balance of the sender and increases the balance of the recipient by the same amount. Other blockchains even allow transactions to create and execute complete programs on the chain.

When a block is added to the blockchain, it propagates to all nodes through the peer-to-peer network. Each node will re-execute all transactions in the block to check their validity, and if they notice any illegal changes, they will reject the block. This means that each transaction is effectively executed once for every single byte across the entire network. This allows the blockchain to be completely decentralized, as each node checks all ongoing transactions, but this comes at a cost: the overhead limits the number of transactions that can be processed.

Note: Public blockchains do not require users to register. They simply need to create a new key pair to start signing transactions to participate in the network; however, they may require users to have currency associated with the blockchain to process their transactions.

Transactions are processed in batches as blocks, which are then linked together to form the actual blockchain. These blocks constitute the history of the blockchain, with each block packing a set of transactions that change its state. The way transactions are selected and ordered within each block depends on the consensus rules of the blockchain, which we will see on later pages.

When a block is added to the blockchain, it propagates to all nodes through the peer-to-peer network. Each node will re-execute all transactions in the block to check their validity, and if they notice any illegal changes, they will reject the block. This means that each transaction is effectively executed once for every single byte across the entire network. This allows the blockchain to be completely decentralized, as each node checks all ongoing transactions, but this comes at a cost: the overhead limits the number of transactions that can be processed.

Transactions are processed in batches as blocks, which are then linked together to form the actual blockchain. These blocks constitute the history of the blockchain, with each block packing a set of transactions that change its state. The way transactions are selected and ordered within each block depends on the consensus rules of the blockchain, which we will see on later pages.

When a block is added to the blockchain, it propagates to all nodes through the peer-to-peer network. Each node will re-execute all transactions in the block to check their validity, and if they notice any illegal changes, they will reject the block. This means that each transaction is effectively executed once for every single byte across the entire network. This allows the blockchain to be completely decentralized, as each node checks all ongoing transactions, but this comes at a cost: the overhead limits the number of transactions that can be processed.

The network processes transactions per second. In other words, blocks are exchanged based on the number of transactions.

Given the high cost of processing changes to a blockchain, all transaction fees must be paid. These fees are typically paid in a currency native to the blockchain (such as Bitcoin in the Bitcoin network or Ether in Ethereum). Regardless of who benefits from these fees, we will see in a few pages that the goal of these fees is to prevent attackers from overwhelming the network with transactions that need to be processed by each node and to reward nodes that add new blocks to the chain.

Blockchains resist changes by storing a summary of their entire history on each block. Each block in the chain is identified by a hash calculated from its own transactions and the hash of the previous block.

How to construct a block. Each block is identified by the hash of the previous block and all its transactions. We will see the role of "now" in the next section.

With this scheme, any change to any transaction on any block in the chain will result in changes to all subsequent hashes, making any modification trivial. For example, if an attacker tries to change a transaction that occurred ten blocks ago, not only will the hash of that block change, but the hash of the next block will also change (since it is calculated based on the hash of the previous block), and this continues all the way to the head of the chain. However, to prevent attackers from modifying the blockchain and distributing false copies in the network, this mechanism must distinguish between regenerating all blocks for the attacker. This is the source of proof of work.

Transactions are ordered, and the inclusion of transactions in the blocks of the blockchain will depend on the decay of the network. Since we are dealing with a decentralized database, we need a way for all participants to agree on how to add changes. For example, if a seller offers an asset on the blockchain and two buyers compete to purchase it, how can a decentralized network decide who is first? Worse, how can we prevent the seller from telling both buyers that they made purchases and cash transactions? "We need a way to determine how transactions are selected and ordered to maintain a single state of the blockchain." In other words, we need a way to determine which blocks to add to.

Many public blockchains, such as Bitcoin or Ethereum, rely on a consensus algorithm known as "proof of work." Proof of work is a cryptographic proof of a large number of CPU cycles used to perform computations; in this case, it is based on calculating a difficult number from the block. To add a block to the blockchain, it must be accompanied by its proof, and the nodes that add the block will be rewarded for their efforts. Nodes that complete this role are called "miners," and they compete to add the next block to receive the corresponding reward. Note that the mechanism for these proofs is actually quite simple. The identifier of the front block in the chain is a hash that includes the identifier of the previous block, all transactions in the block, and a nonce. By changing the current value, the calculated hash of the block will be completely different. To add a new block to the chain, the thief must have a specific structure (starting with N zeros). Since predicting what the hash will look like is not passive, miners can only repeatedly calculate the block while changing the nonce until they find a hash that meets the requirements. This requires many attempts and is therefore considered proof of work.

Keep in mind that the entire infrastructure runs on a peer-to-peer decentralized network. This allows nodes to run and leave the network at will without requiring a centralized server. Here, the Proof-of-Work algorithm provides a way for new nodes to know which is the actual chain: they just need to look for the chain with the highest accumulated computational power.

This also prevents malicious participants from merely changing records on the chain and recalculating all subsequent block hashes, as we discussed in the previous section. To do this, an attacker would need to solve all the proof of work from the attacked blocks, which requires more computational power than other miners in the network.

Note that besides proof of work, there are other consensus mechanisms. Inside... proof of authority and proof of stake serve as alternatives for establishing faster and smaller chains.

The aging algorithm is closely related to the finality of the network. When we know that it has been included in the blockchain and will not change, we say that the exchange is final. Transactions added in recent blocks are far from being considered final: if a miner manages to mine two blocks in a row, starting from EXT to the last, they may generate a new chain that replaces the latest block without including that transaction.

This is called a reorganization, which is not uncommon in proof-of-work chains. To know that a transaction is final, we need to wait for several blocks to be mined above the transaction to ensure that it will not change. The number of blocks will depend on the specific chain and how much confidence we need.

Throughput is designed to solve a proof of work that is computationally expensive. This itself enforces a dependency on blockchain throughput, forcing a difficult problem to be solved each time a large number of transactions are added. However, there is another reason that limits the number of transactions added to the chain per second: verifiability. To maintain the decentralization of the blockchain, each node in the network needs to be able to verify that each transaction is legitimate and executed according to established rules. If the network accepts a large number of transactions per second, then only powerful devices will be able to verify the chain, leaving the network inaccessible to any users who cannot access the necessary hardware. Therefore, low throughput is related to ensuring public access to the blockchain.

Specifically, Ethereum is designed to handle about 15 transactions per second. Note that transactions in Ethereum can be more complex because they can execute arbitrary computations, so this limit is actually related to how much effort is needed to run and verify each transaction.

Note that these several transactions are shared among all users and applications in the network per second. Even for a traditional web application, this is a very low limit. We will see some methods around this limit in Chapter 8.

From Bitcoin to Ethereum, so far, WWE has treated a block of data as a public database, but we have not yet delved into what this database might contain. The first famous blockchain was used to track ownership of a digital currency, Bitcoin.

The blockchain as we understand it today was largely introduced in 2008 by Satoshi Nakamoto in his paper "Bitcoin: A Peer-to-Peer Electronic Cash System." This paper is both short and easy to read, and it encapsulates most of the blockchain concepts used today. It introduces a "purely peer-to-peer version of electronic cash, allowing online payments to be sent directly from one party to another without going through a financial institution."

In summary, the Bitcoin blockchain is a public decentralized database that tracks user balances of Bitcoin and supports transactions transferring funds from one auditing agency to another, representing an implementation of a deeply centralized electronic payment platform. It is worth mentioning that, in addition to ordinary transfers, Bitcoin also supports a limited scripting language. This language allows for constructs such as time locks, which restrict execution to a future period, or multi-signature transactions that require multiple accounts to agree to move assets. What can be built with this language is still limited. It was proposed specifically to support arbitration computations within the network.

Cryptography has a long history, primarily applied in the transmission of military secrets in ancient times, such as "passwords," "codes," etc. Before 1970, the application of cryptography was mostly at the government level, until the invention of standard encryption systems—the Data Encryption Standard and asymmetric encryption algorithms—cryptography began to be deeply applied in various fields.

The Development of Cryptography#

The development of cryptography can be roughly divided into three stages: classical cryptography -> modern cryptography -> public key cryptography.

  1. Classical cryptography: The core cryptographic ideas of this stage mainly involve substitution and transposition. Substitution means replacing each character of plaintext with another character to produce ciphertext, and the recipient can obtain plaintext by replacing ciphertext with corresponding characters. Transposition means scrambling the order of characters in plaintext according to certain rules.
  2. Modern cryptography: The development of this stage mainly involves symmetric encryption algorithms. Symmetric encryption means that the sender uses a certain public algorithm to encrypt plaintext with a key, and the recipient uses the key provided by the sender to decrypt ciphertext to obtain plaintext.
  3. Public key cryptography: The development of this stage mainly involves asymmetric encryption algorithms. The principle of asymmetric encryption is public key encryption and private key decryption. The implementation process is that A generates a pair of keys through a certain algorithm, namely a public key and a private key, and then publicly shares the public key. If B wants to send information to A, they encrypt the plaintext with A's public key to produce ciphertext and send it to A. Upon receiving the ciphertext, A uses their private key to decrypt it and obtain the plaintext.

Asymmetric encryption involves: public keys and private keys.

Characteristics include:

  • Feature 1: Data encrypted with the public key can only be decrypted with the private key; the public key cannot decrypt it.
  • Feature 2: Data encrypted with the private key can only be decrypted with the public key; the private key cannot decrypt it.
  • The server holds both the public key and the private key (not given to anyone).
  • The server gives its public key to whoever it wants to communicate with.

The interaction process using asymmetric encryption is as follows: The client first obtains the server's public key, then uses this public key to encrypt data, and sends the encrypted data to the server. Due to the aforementioned features, only the server can correctly decrypt the data.

Cryptography is widely applied in blockchain and can be divided into three categories: symmetric encryption algorithms, asymmetric encryption algorithms, and hash algorithms. Common methods include: Merkle tree hash algorithms, elliptic curve algorithms, SHA-256 algorithms, and Base58 encoding. Their functions include: quickly searching through hash algorithms; encrypting and decrypting plaintext; signing and verifying information; generating digital certificates; generating account addresses, etc.

Symmetric encryption algorithms are the earliest encryption algorithms, and the technology is mature.

  1. The process of using symmetric encryption

In symmetric encryption algorithms, the data sender processes the plaintext (original data) and the encryption key through a special encryption algorithm to transform it into complex encrypted ciphertext. Upon receiving the ciphertext, the recipient must use the key used for encryption and the inverse algorithm of the same algorithm to decrypt the ciphertext to restore it to readable plaintext. In symmetric encryption algorithms, only one key is used, and both the sender and receiver use this key to encrypt and decrypt data, which requires the decrypting party to know the encryption key in advance.

  1. Characteristics of symmetric encryption

The characteristics of symmetric encryption algorithms are that the algorithms are public, the computational load is small, the encryption speed is fast, and the encryption efficiency is high. The downside is that both parties to the transaction use the same key, which does not guarantee security. In addition, each pair of users must use a unique key that others do not know each time they use symmetric encryption algorithms, which causes the number of keys held by the sender and receiver to grow geometrically, making key management a burden for users. For example, if two users need to use symmetric encryption methods to encrypt and then exchange data, the users need at least two keys and to exchange them. If there are n users within an enterprise, the entire enterprise will need n × (n - 1) keys, and the generation and distribution of keys will become a nightmare for the enterprise's information department. The security of symmetric encryption algorithms depends on how well the encryption key is kept, but it is impossible to require every person holding a key in the enterprise to keep it secret; they often inadvertently leak the key—if a key used by a user is obtained by an intruder, the intruder can read all documents encrypted with that user's key. If the entire enterprise shares one encryption key, then the confidentiality of all enterprise documents is compromised.

Symmetric encryption algorithms are more difficult to use in distributed network systems, mainly due to the difficulty of key management and high usage costs. Compared to public key encryption algorithms, or asymmetric encryption algorithms, symmetric encryption algorithms can provide encryption and authentication but lack signature functionality, which limits their scope of use.

  1. Classification of symmetric encryption

Symmetric encryption is divided into stream ciphers and block ciphers.

  • Stream ciphers encrypt each byte of plaintext in sequence. Encryption refers to using the user's key to generate a large amount of pseudo-random flow through some complex operations (cryptographic algorithms) to encrypt the plaintext stream. Decryption refers to using the same key and cryptographic algorithm, along with the same pseudo-random flow, to restore the plaintext stream.
  • Block ciphers encrypt one block of plaintext at a time. It divides plaintext into groups of a fixed bit length, and the plaintext group undergoes encryption operations to obtain a ciphertext group. The ciphertext group undergoes decryption operations (the inverse operation of the encryption operation) to restore it to the plaintext group.

Stream ciphers, also known as stream ciphers, use a seed key to generate a pseudo-random sequence that matches the length of the plaintext. When encrypting a message m using a stream cipher, it is encrypted byte by byte, generally by first dividing m into consecutive characters, m = m1m2m3…; then using the i-th character ki from the key stream k = k1k2k3... to perform the encryption transformation on the i-th character mi, i = 1, 2, 3...; all encryption outputs concatenated together form the ciphertext after encrypting m. Decryption needs to be synchronized with encryption, so when using stream ciphers, the sender and receiver need to operate on the same position of the plaintext or ciphertext.

The encryption and decryption process is illustrated as follows:

image

Stream ciphers have the characteristics of simple implementation, ease of hardware implementation, fast encryption and decryption processing speed, and limited error propagation. However, because these ciphers are mainly used in military and political secret agencies, their research results are less publicly available. Currently, algorithms that can be publicly applied in other fields include RC4, SEAL, A5, etc.

One-Time Pad Encryption#

One-time pad encryption is an ideal encryption scheme proposed jointly by Mauborgne and Vernam in 1917. It requires encrypting each character of the plaintext message independently, with each character's encryption using a completely random key sequence that is as long as the plaintext. This key sequence is the one-time pad. When used, both the sender and receiver have an identical one-time pad, which is a sufficiently long random key sequence determined by both parties. When the sender encrypts the plaintext, the key sequence used comes from the front segment of the one-time pad corresponding to the length of the message. After encryption, the used key sequence is immediately destroyed. Upon receiving the ciphertext, the receiver uses the key sequence from the one-time pad to decrypt the ciphertext in sequence, obtaining the plaintext, while also destroying the used key sequence and not using it again.

The one-time pad is considered an unconditionally secure cryptographic system, meaning it is an unbreakable encryption system. If an eavesdropper obtains the ciphertext, there is no way to decrypt it because the encryption key is completely random and has no pattern; the attacker has no information to analyze the ciphertext. However, the premise is that the one-time pad must not be leaked.

The introduction of the one-time pad concept provides direction for the generation of stream ciphers. It is based on this concept that more and more stream cipher algorithms continue to emerge.

Structure of Stream Ciphers#

The structure of stream ciphers can be subdivided into synchronous stream ciphers and self-synchronous stream ciphers. Synchronous stream ciphers refer to those whose key stream generation is independent of the plaintext, but rather generated by some independent random method. Self-synchronous stream ciphers, also known as asynchronous stream ciphers, are the opposite of synchronous stream ciphers, where the key stream generation is related to the plaintext stream and specifically where the generation of the next key word is related to the previously encrypted word.

Synchronous Stream Ciphers#

The process of generating the cipher stream in synchronous stream ciphers is divided into two parts: one is the key stream generator, and the other is the encryption transformer.
The encryption process expression is: ci = E(ki, mi), where parameters are single elements of byte arrays. The decryption process must be synchronized with the encryption process, and the expression is the same. Since the generation of the key stream is different each time, during encryption, the elements generated in the key stream are first cached in a register, and after the decryption of this element is completed, encryption continues. The entire process is somewhat similar to the TCP protocol. The most commonly used stream cipher system is binary addition stream ciphers over finite fields GF(2), whose encryption transformation can be expressed as ci = ki⊕mi.

Characteristics:

In synchronous stream ciphers, the sender and receiver must be synchronized, meaning both parties use the same key to operate on the same position. If a ciphertext character is lost, damaged, or deleted during transmission, decryption will fail.
2) No error propagation.
If a ciphertext character is modified during transmission, it only affects that character and does not affect the decryption of other ciphertext characters.
3) Active attacks can destroy synchronization.
As a result of the synchronization requirement, active attackers can replay, insert, delete, and otherwise disrupt the ciphertext characters during transmission, directly causing the synchronization of the encryption and decryption process to fail. Therefore, when using it, other cryptographic techniques need to be employed to authenticate and verify the integrity of the transmitted ciphertext.

2.2 Self-Synchronous Stream Ciphers

The key stream generation of self-synchronous ciphers is not independent of the plaintext and ciphertext streams, typically where the generation of the i-th key word is related to several previously generated ciphertext words.
Characteristics:

  1. Self-synchronization.
    If certain ciphertext characters are attacked during the transmission of the ciphertext stream, the decryption of the receiver will only be out of sync with the attacked ciphertext, while the decryption of other ciphertext streams will not have issues.
  2. Limited error propagation.
    The decryption of the receiver only affects the attacked i ciphertext characters, while other ciphertext streams will not have issues. Therefore, the plaintext generated can have at most i errors.
  3. Active attacks destroy current synchronization.
  4. Plaintext statistical expansion.
    Each plaintext character affects the entire subsequent ciphertext, meaning the statistical characteristics of the plaintext are diffused into the ciphertext. Therefore, self-synchronous stream ciphers are stronger against attacks that exploit plaintext redundancy than synchronous stream ciphers.

2.3 Key Stream Generator

The key stream is an important part of the stream cipher. An ideal key stream generator produces a completely random key stream, but in practice, since it is generated based on the user's private key through a certain algorithm, it cannot achieve true randomness, so the generated key stream is a pseudo-random sequence.
A key stream generator typically consists of a linear feedback shift register (LFSR) and a non-linear combination part. The linear feedback shift register can be referred to as the driving part. Its working principle is to take the state variable x of the driving part at time j as input to the non-linear combination part f, and use f(x) as the current value of kj. The driving part is responsible for providing the sequence used by the non-linear combination part, while the non-linear part combines the state of the shift register at each moment to produce the value of the key sequence at time j. In simple terms, the internal variables of the driving part are constantly changing, and the values at each moment are different. It continuously inputs the values of variable x at different moments into the non-linear combination part, which receives the current value of x and produces the current key stream byte through function f.

2.4 Feedback Shift Register

The feedback shift register is an important component of the key stream generated by stream ciphers. An n-level feedback shift register over GF(2) consists of n binary storage elements and a feedback function f(a1, a2, a3, a4,..., an). The n-level feedback shift register is shown in the figure below.

image

Each storage element is referred to as a level of the shift register. At any moment, the contents of these levels constitute the state of the feedback shift register, and each state corresponds to a vector over GF(2), with a total of 2^n possible states. The state at each moment can be represented by a sequence of length n: a1, a2, a3,..., an, or an n-dimensional vector (a1, a2, a3,..., an), where ai is the content of the i-th storage element at the current moment. The initial state is determined by the user, and when the i-th shift clock pulse arrives, each level of storage ai passes its content to the lower level ai-1, and the feedback function f(a1, a2, a3, a4,..., an) calculates the next state an based on the current state of the register. The feedback function is an n-ary Boolean function, meaning that the n variables a1, a2, a3,..., an can independently take the values 0 and 1, and the operations in the function include logical AND, logical OR, logical NOT, etc., with the final function value being either 0 or 1.

RC4 Algorithm#

The RC4 encryption algorithm is a variable-length stream encryption algorithm designed by Ron Rivest, one of the RSA trio, in 1987. The speed of this algorithm can reach about 10 times that of DES encryption and has a very high level of non-linearity. In September 1994, its algorithm was published on the internet. Since RC4 encryption uses XOR, once the subkey sequence appears to repeat, the ciphertext may be cracked. As an old verification and encryption algorithm, RC4 is now gradually not recommended for use.

The implementation of the RC4 algorithm is very simple. It initializes a state vector S of 256 bytes using a variable-length key from 1 to 256 bytes (8 to 2048 bits), with elements denoted as S[0], S[1], S[2],..., S[255]. S is first initialized as S[i] = i. It always contains all 8-bit numbers from 0 to 255, just undergoing permutation operations. Each generated key byte ki is selected from one element of the 256 elements in S according to a certain method. Each time a key byte is generated, the elements in the S vector undergo a permutation operation. Thus, the RC4 algorithm is divided into two parts: initializing S and generating the key stream, where each time a key byte is generated, it is XORed with the corresponding plaintext element to obtain the ciphertext.

1.1 Initializing S

The steps to generate S are as follows:

  1. Declare a byte array of length 256 and fill the elements of S from 0 to 255 in ascending order, i.e., S[0] = 0, S[1] = 1, S[2] = 2,..., S[255] = 255.
  2. j := 0
  3. For 0 <= i <= 255, loop the following two methods:
    j = (j + S[i] + int(K[i%keylen])) % 256
    S[i], S[j] = S[j], S[i]

1.2 Generating the Key Stream

The steps are as follows:

  1. i = 0; j = 0
  2. i = (i + 1) % 256
  3. j = (j + S[i]) % 256
  4. S[i], S[j] = S[j], S[i]
  5. Output the key byte key = S[(S[i] + S[j]) % 256]

1.3 Security of RC4

Since the RC4 algorithm uses XOR for encryption, once the subkey sequence appears to repeat, the ciphertext may be cracked. However, no repetition has been found for RC4 with a key length of 128 bits, so RC4 is still considered one of the safest encryption algorithms.

1.4 RC4 Encryption Process

A brief introduction to the RC4 encryption process:

  1. Using its own key, generate a key stream generator.
  2. The key stream generator produces a pseudo-random sequence based on the length of the plaintext.
  3. Each bit of the pseudo-random sequence is XORed with the corresponding bit of the plaintext to generate the ciphertext.
    Illustration:

image

Golang Implementation of RC4 Encryption:#

package main

import "fmt"

func main() {
    data := []byte("helloworld")
    output := make([]byte, len(data))
    fmt.Printf("Plaintext: %s\n", data)

    K := []byte("qwuoaknfabbalafbj")
    keylen := len(K)
    SetKey(K, keylen)

    output = Transform(output, data, len(data))
    fmt.Printf("Ciphertext: %x\n", output)

    SetKey(K, keylen)
    output1 := make([]byte, len(data))
    output1 = Transform(output1, output, len(data))
    fmt.Printf("Decrypted Plaintext: %s", output1)
}

var S = [256]int{}
// Initialize S-box
func SetKey(K []byte, keylen int) {
    for i := 0; i < 256; i++ {
        S[i] = i
    }

    j := 0
    for i := 0; i < 256; i++ {
        j = (j + S[i] + int(K[i%keylen])) % 256
        S[i], S[j] = S[j], S[i]
    }
}

// Generate key stream
func Transform(output []byte, data []byte, length int) []byte {
    i := 0
    j := 0
    output = make([]byte, length)

    for k := 0; k < length; k++ {
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        key := S[(S[i] + S[j]) % 256]
        // Bitwise XOR operation
        output[k] = uint8(key) ^ data[k]
    }
    return output
}

Using the encapsulated method in Golang to implement RC4 encryption, but without decryption.

package main

import (
    "fmt"
    "crypto/rc4"
)

func main() {
    var key []byte = []byte("fd6cde7c2f4913f22297c948dd530c84") // Initialize the key for encryption
    rc4obj, _ := rc4.NewCipher(key) // Return Cipher

    str := []byte("helloworld")  // The string to be encrypted
    plaintext := make([]byte, len(str)) //
    rc4obj.XORKeyStream(plaintext, str)
    // XORKeyStream method XORs the data from src with the pseudo-random bit stream generated by the key and writes it to dst.
    // plaintext is the result returned after encryption, note: plaintext is a base-16 encoded string, each byte is represented by 2 characters and must be formatted as a string.

    stringinf := fmt.Sprintf("%x\n", plaintext) // Convert to string
    fmt.Println(stringinf)
}

Block ciphers, also known as block encryption, generally first pad the plaintext m to obtain a plaintext string M that is a multiple of a fixed block length s; then divide M into blocks of length s; finally, encrypt each block using the same key. Common algorithms include AES, DES, and 3DES.

In block ciphers, whether plaintext blocks or ciphertext blocks, there are some logical relationships between blocks, and these relationships are the operation modes.
Common operation modes for block ciphers include:

  • Electronic Code Book (ECB)
  • Cipher Block Chaining (CBC)
  • Cipher Feedback Mode (CFB)
  • Output Feedback Mode (OFB)
  • Counter mode (CTR)

Currently, CBC mode and CTR mode are recommended, while other modes are less commonly used or not recommended.

  1. ECB Mode

ECB, also known as Electronic Code Book mode, is the most basic block cipher encryption mode. Before encryption, the plaintext is divided into several blocks based on the encryption block size (e.g., 128 bits for AES). If the last block is less than 128 bits, padding is used (specifics depend on the algorithm, default is 0x00). Each block is then encrypted separately using the same key to obtain ciphertext blocks, which are then concatenated to produce the final ciphertext. Decryption follows the same principle.
The following diagram illustrates the encryption process of ECB mode:

Decryption process:

From this, we can see that the same plaintext will always be encrypted into the same ciphertext, and the format of the ciphertext is the same as that of the plaintext. This is very insecure, especially in cases where images or plaintext content is repeated many times. Since the encryption method for all blocks is consistent, repeated content in the plaintext will be reflected in the ciphertext, making it difficult to resist statistical analysis attacks. Additionally, because the order of the plaintext and ciphertext content is the same, attackers can easily disrupt the ciphertext. If an attacker intercepts the ciphertext during transmission and disrupts the order of the ciphertext content, the recipient will not be able to decrypt it back to the original plaintext. This is also why ECB mode is rarely used.

Characteristics:

  1. Simple operation, easy to implement, conducive to parallel computation, and errors will not be transmitted;
  2. Cannot hide the pattern of the plaintext;
  3. May be subject to active attacks on the plaintext.

CBC Mode#

CBC, also known as Cipher Block Chaining mode, is named because the ciphertext blocks are linked together like a chain.

In CBC mode, each plaintext block is first XORed with the previous ciphertext block before encryption. In this method, each ciphertext block depends on all previous plaintext blocks. Additionally, to ensure the uniqueness of each message, an initialization vector must be used for the first block.
If the index of the first block is 1, the encryption process of CBC mode is as follows:
Ci = Ek (P ⊕ Ci-1), C0 = IV.
The decryption process is:
Pi = Dk (Ci) ⊕ Ci-1, C0 = IV.

The operation process of the CBC algorithm is illustrated below:

image

Advantages of the CBC algorithm:

  1. Repeated arrangements of plaintext will not be reflected in the ciphertext.
  2. Supports parallel computation (only for decryption).
  3. Can decrypt any ciphertext block.

Disadvantages of the CBC algorithm:

  1. When decrypting ciphertext containing certain erroneous bits, all bits of the first block and the corresponding bits of the next block will be erroneous.
  2. Encryption does not support parallel computation.

CFB, also known as Cipher Feedback, is similar to CBC and can convert block ciphers into self-synchronous stream ciphers; the working process is also very similar. It requires a shift register of the same size as the block and initializes the register with IV. Then, the contents of the register are encrypted using the block cipher, and the highest x bits of the result are XORed with the x bits of the plaintext to produce the x bits of ciphertext. The next x bits of ciphertext are then moved into the register, and this process is repeated for the next x bits of plaintext. The decryption process is similar to the encryption process, starting with IV, encrypting the register, and XORing the high x bits with the ciphertext to produce the x bits of plaintext, then moving the next x bits of ciphertext into the register.
Similar to CBC, changes to the plaintext will affect all subsequent ciphertext, so the encryption process cannot be parallelized; however, the decryption process can be parallelized.

The operation process of the CFB mode is illustrated below:

image

Advantages of the CFB mode:

  1. No need for padding.
  2. Supports parallel computation (only for decryption).
  3. Can decrypt any ciphertext block.

Disadvantages of the CFB mode:

  1. Encryption does not support parallel computation.
  2. When decrypting ciphertext containing certain erroneous bits, all bits of the first block and the corresponding bits of the next block will be erroneous.
  3. Cannot resist replay attacks.

OFB: Runs the block cipher as a synchronous stream cipher, similar to CFB, but OFB uses the output of the previous n bits of ciphertext to feed back into the shift register. OFB does not have error propagation issues.
The Output Feedback Mode (OFB) can turn block ciphers into synchronous stream ciphers. It generates key stream blocks and then XORs them with plaintext blocks to obtain ciphertext. Like other stream ciphers, flipping one bit in the ciphertext will also flip the corresponding bit in the plaintext. This property allows many error correction codes, such as parity bits, to yield correct results even if calculated before encryption and checked after encryption.
Each output block used in OFB is related to all previous output blocks, so it cannot be processed in parallel. However, since plaintext and ciphertext are only used in the final XOR process, IV can be encrypted in advance, allowing plaintext or ciphertext to be processed in parallel during the final XOR operation.
It is practical to generate the key stream for the OFB mode using the CBC mode with an input of all zeros, as this allows for faster encryption processes using fast CBC hardware implementations.

Encryption process:

image

Advantages of the OFB mode:

  1. No need for padding.
  2. Can prepare for encryption and decryption in advance.
  3. Uses the same structure for encryption and decryption.
  4. When decrypting ciphertext containing certain erroneous bits, only the corresponding bits in the plaintext will be erroneous.

Disadvantages of the OFB mode:

  1. Does not support parallel computation.
  2. Active attackers reversing certain bits in the ciphertext block will also reverse the corresponding bits in the plaintext block.

CTR Mode#

Counter mode (CTR mode) encryption encrypts a series of input data blocks (called counters) to produce a series of stream ciphers, which are XORed with the plaintext to obtain ciphertext. Similarly, decryption is performed by XORing the stream cipher with the ciphertext to obtain plaintext.
The data blocks are generated before encryption by incrementing the counter, which consists of a nonce and a counter (block number). The CTR counter is 128 bits long (16 bytes). The first 8 bytes are called the nonce, which is different for each encryption. The last 8 bytes are the block number, which is incremented continuously. The nonce serves to complicate the content of the data blocks. Without the nonce, if only the counter is used, the data blocks would be too uniform. The implementation of the counter in Golang is slightly different from what is described here; it first initializes an initial vector iv of length BLOCK.SIZE(), then increments the last byte of iv by one for each group, which also generates different data blocks before block encryption.
The encryption process is to generate an initial counter. Suppose there are 8 blocks, the initial counter is incremented continuously to obtain 8 counter values, each of which is then encrypted to produce a key stream, and each key stream is XORed with the corresponding plaintext block to obtain ciphertext. Thus, its encryption process is equivalent to one-time pad encryption.

In CTR mode, blocks can be encrypted and decrypted in any order because the values of the "counter" needed for encryption and decryption can be calculated directly from the nonce and block number. This means that parallel computation can be achieved. In systems that support parallel computation, the speed of CTR mode is very fast.
The following diagram illustrates the encryption and decryption process of CTR mode:

unc AesCTR_Encrypt(plainText,key[]byte)[]byte{
	// Check if the key passed by the user meets 16 bytes; if not, handle it.
	keylen:=len(key)
	if keylen==0{   // If the user passes in an empty key, use the default key.
		key=[]byte("wumansgygoaescbc")   // Default key
	}else if keylen>0&&keylen<16{  // If the key length is between 0 and 16, pad the remaining with 0.
		key=append(key,bytes.Repeat([]byte{0},(16-keylen))...)
	}else if keylen>16{
		key=key[:16]
	}
	//1. Specify the AES encryption algorithm to use.
	block, err := aes.NewCipher(key)
	if err!=nil{
		panic(err)
	}

	//2. No padding is needed; directly obtain the stream of the CTR block mode.
	// Returns a stream interface generated by the block that uses the key stream, and the length of the initial vector iv must equal the block's block size.
	iv := []byte("wumansgygoaesctr")
	stream := cipher.NewCTR(block, iv)

	//3. Encryption operation
	cipherText := make([]byte,len(plainText))
	stream.XORKeyStream(cipherText,plainText)

	return  cipherText
}

func AesCTR_Decrypt(cipherText,key []byte)[]byte{
	// Check if the key passed by the user meets 16 bytes; if not, handle it.
	keylen:=len(key)
	if keylen==0{   // If the user passes in an empty key, use the default key.
		key=[]byte("wumansgygoaescbc")   // Default key
	}else if keylen>0&&keylen<16{  // If the key length is between 0 and 16, pad the remaining with 0.
		key=append(key,bytes.Repeat([]byte{0},(16-keylen))...)
	}else if keylen>16{
		key=key[:16]
	}
	//1. Specify the algorithm: aes
	block, err:= aes.NewCipher(key)
	if err!=nil{
		panic(err)
	}
	//2. Return a stream interface generated by the block that uses the key stream, and the length of the initial vector iv must equal the block's block size.
	iv := []byte("wumansgygoaesctr")
	stream := cipher.NewCTR(block, iv)

	//3. Decryption operation
	plainText := make([]byte,len(cipherText))
	stream.XORKeyStream(plainText,cipherText)

	return plainText
}

Asymmetric encryption, also known as public key cryptography, was first proposed by Diffie and Hellman in 1976 as a revolutionary encryption idea. At that time, almost all cryptographic systems were symmetric cryptosystems, based on simple methods like substitution and transposition. Public key cryptography is completely different; it is asymmetric, involving two different keys: a public key and a private key, and the encryption principle is not based on simple substitution or transposition but on complex mathematical functions. These mathematical functions are based on mathematical problems, generally divided into three categories: large integer factorization problems, discrete logarithm problems, and elliptic curve problems. Sometimes the elliptic curve category is classified under discrete logarithm problems. Public key cryptography represents a revolutionary change, breaking the original cryptographic model and solving two major problems of traditional cryptography: key distribution and digital signatures.

  1. Overview of Asymmetric Encryption

Traditional cryptographic systems use a single key, and the cost of transmitting the key from the sender to the receiver is very high and risky. If the ciphertext received is modified during transmission, the receiver cannot determine the authenticity of the ciphertext. The public key system perfectly solves the above problems. It has a pair of keys: one is the public key, which is completely public and can be received by anyone; the other is the private key, which is kept secret and does not need to be disclosed to anyone. The public key cannot be used to calculate the private key, so the private key is secure. The sender A encrypts the plaintext using the public key, and the receiver B decrypts it using the corresponding private key. To ensure the integrity of the transmitted ciphertext and the accuracy of the message source, the ciphertext must be digitally signed. A encrypts the ciphertext with their private key, a process called digital signing; B receives the ciphertext and decrypts it using the corresponding public key, a process called signature verification.

Thus, public key cryptography can be divided into two models: the encryption-decryption model and the signing-verification model. The two models can be used independently or together, depending on the application scenario. Generally, the transmitted ciphertext requires digital signatures, and the sent content includes both the ciphertext and the signature. The recipient first verifies the signature, and if the verification passes, they proceed to decrypt.

Asymmetric encryption methods are numerous, and the following discusses RSA, DSA, and ECDSA as three encryption methods.

Requirements for Public Key Cryptography#

To implement public key cryptography, the following requirements must be met:

  1. A pair of key pairs must be generated, namely the public and private keys, which is computationally easy;
  2. It must be easy to encrypt plaintext using the public key;
  3. It must be easy to decrypt ciphertext using the private key;
  4. It must be impossible to calculate the private key from the public key;
  5. It must be impossible to calculate the plaintext from the public key and ciphertext;
  6. The order of encryption and decryption can be exchanged.

Currently, there are many difficult problems that meet the above requirements for establishing public key cryptography. I will only analyze two commonly used ones:

  1. Large Integer Factorization Problem
    If two large prime numbers p and q are known, it is easy to compute n = pq, but if n is known, it is almost impossible to find p and q. This is the large integer factorization problem.
  2. Discrete Logarithm Problem
    First, understand two concepts: order and primitive root.
    Let m > 1 and (a, m) = 1, then the smallest positive integer t that satisfies a^t ≡ 1 mod m is called the order of a modulo m, denoted as δm(a).
    A primitive root is a mathematical symbol. Let m be a positive integer and a be an integer. If the order of a modulo m is equal to φ(m), then a is called a primitive root modulo m. The φ(m) mentioned is the number of prime factors of m.
    Given a formula a^t mod b ≡ c, where a is a primitive root of b, b is a very large prime number, and c is a positive integer less than b. The problem is that it is easy to find c given a, t, b, but it is very difficult to find t given a, b, c. The process of solving this is extremely complex, and the number of t satisfying the condition is numerous. Here, if b is replaced with a large number, it is almost impossible to find the actual t.

RSA Algorithm#

RSA is one of the most widely used public key cryptography systems. It was proposed in 1977 by Ronald Rivest, Adi Shamir, and Leonard Adleman while they were working at the Massachusetts Institute of Technology. RSA is formed by combining the first letters of their surnames.
The security of the RSA algorithm is based on the difficulty of the RSA problem, which is based on the difficulty of large integer factorization. However, the RSA problem is not necessarily more difficult than the factorization problem, meaning that it may be possible to solve the RSA problem without solving the factorization problem, so the RSA algorithm is not entirely based on the difficulty of large integer factorization.

  1. Description of the RSA Algorithm

1.1 Generating Public and Private Key Pairs in RSA

To illustrate how to generate key pairs, consider the following steps:

  1. Randomly select two distinct prime numbers p and q.
    Alice chooses 61 and 53. (In practical applications, the larger these two primes are, the harder they are to crack.)

  2. Calculate the product of p and q, n.
    n = 61 × 53 = 3233
    The length of n is the key length. 3233 written in binary is 110010100001, which has 12 bits, so this key is 12 bits long. In practical applications, RSA keys are generally 1024 bits long, and in important cases, 2048 bits.

  3. Calculate the Euler's function φ(n) of n.
    φ(n) = (p - 1)(q - 1)
    Alice calculates φ(3233) to be 60 × 52, which equals 3120.

  4. Randomly select an integer e, which is used to encrypt the public key.
    The condition is 1 < e < φ(n), and e must be coprime to φ(n).

    Alice randomly selects 17 between 1 and 3120. (In practical applications, 65537 is often chosen.)

  5. Calculate the modular inverse d of e with respect to φ(n).
    The so-called "modular inverse" means there exists an integer d such that ed leaves a remainder of 1 when divided by φ(n). ed ≡ 1 (mod φ(n))
    Alice finds 2753, meaning 17 × 2753 mod 3120 = 1.

  6. The public key is formed by (n, e), and the private key is formed by (n, d).
    In Alice's example, n = 3233, e = 17, d = 2753, so the public key is (3233, 17) and the private key is (3233, 2753).

1.2 RSA Encryption

First, the plaintext is grouped into bit strings, ensuring that each group corresponds to a decimal number less than n, and then each group m is encrypted one by one. The sequence of ciphertexts formed by all groups is the encryption result, meaning m satisfies 0 <= m < n, and the encryption algorithm is:
c ≡ m^e mod n; where c is the ciphertext, and 0 <= c < n.

1.3 RSA Decryption

For ciphertext 0 <= c < n, the decryption algorithm is:
m ≡ c^d mod n;

1.4 RSA Signature Verification

The RSA cryptographic system can be used for both encryption and digital signatures. The following describes the functionality of RSA digital signatures.
Given the public key (e, n) and private key d:

  1. For message m, the signature is: sign ≡ m^d mod n
  2. Verification: For message signature pair (m, sign), if m ≡ sign^e mod n, then sign is a valid signature for m.

2. Golang Implementation of RSA Encryption and Decryption with Known Public and Private Keys#

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "errors"
    "fmt"
)

// Can be generated through openssl
// openssl genrsa -out rsa_private_key.pem 1024
var privateKey = []byte(`   
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDfw1/P15GQzGGYvNwVmXIGGxea8Pb2wJcF7ZW7tmFdLSjOItn9
kvUsbQgS5yxx+f2sAv1ocxbPTsFdRc6yUTJdeQolDOkEzNP0B8XKm+Lxy4giwwR5
LJQTANkqe4w/d9u129bRhTu/SUzSUIr65zZ/s6TUGQD6QzKY1Y8xS+FoQQIDAQAB
AoGAbSNg7wHomORm0dWDzvEpwTqjl8nh2tZyksyf1I+PC6BEH8613k04UfPYFUg1
0F2rUaOfr7s6q+BwxaqPtz+NPUotMjeVrEmmYM4rrYkrnd0lRiAxmkQUBlLrCBiF
u+bluDkHXF7+TUfJm4AZAvbtR2wO5DUAOZ244FfJueYyZHECQQD+V5/WrgKkBlYy
XhioQBXff7TLCrmMlUziJcQ295kIn8n1GaKzunJkhreoMbiRe0hpIIgPYb9E57tT
/mP/MoYtAkEA4Ti6XiOXgxzV5gcB+fhJyb8PJCVkgP2wg0OQp2DKPp+5xsmRuUXv
720oExv92jv6X65x631VGjDmfJNb99wq5QJBAMSHUKrBqqizfMdOjh7z5fLc6wY5
M0a91rqoFAWlLErNrXAGbwIRf3LN5fvA76z6ZelViczY6sKDjOxKFVqL38ECQG0S
pxdOT2M9BM45GJjxyPJ+qBuOTGU391Mq1pRpCKlZe4QtPHioyTGAAMd4Z/FX2MKb
3in48c0UX5t3VjPsmY0CQQCc1jmEoB83JmTHYByvDpc8kzsD8+GmiPVrausrjj4p
y2DQpGmUic2zqCxl6qXMpBGtFEhrUbKhOiVOJbRNGvWW
-----END RSA PRIVATE KEY-----
`)

// openssl
// openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
var publicKey = []byte(`   
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDfw1/P15GQzGGYvNwVmXIGGxea
8Pb2wJcF7ZW7tmFdLSjOItn9kvUsbQgS5yxx+f2sAv1ocxbPTsFdRc6yUTJdeQol
DOkEzNP0B8XKm+Lxy4giwwR5LJQTANkqe4w/d9u129bRhTu/SUzSUIr65zZ/s6TU
GQD6QzKY1Y8xS+FoQQIDAQAB
-----END PUBLIC KEY-----    
`)

// Encrypt
func RsaEncrypt(origData []byte) ([]byte, error) {
    // Decode the PEM format public key to get the public key block
    block, _ := pem.Decode(publicKey)
    if block == nil {
        return nil, errors.New("public key error")
    }
    // Parse to get the public key
    pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
    if err != nil {
        return nil, err // Interface type assertion
    }
    pub := pubInterface.(*rsa.PublicKey)
    // Encrypt
    return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}

// Decrypt
func RsaDecrypt(ciphertext []byte) ([]byte, error) {
    // Decode the PEM format private key to get the private key block
    block, _ := pem.Decode(privateKey)
    if block == nil {
        return nil, errors.New("private key error!")
    }
    // Parse to get the PKCS1 format private key
    priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        return nil, err
    }
    // Decrypt
    return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}

func main() {
    data, _ := RsaEncrypt([]byte("hello world"))
    fmt.Println(base64.StdEncoding.EncodeToString(data))
    origData, _ := RsaDecrypt(data)
    fmt.Println(string(origData))
}

Golang Implementation of RSA Key Generation and Encryption/Decryption#

package main

import (
    "crypto/rsa"
    "crypto/rand"
    "fmt"
    "crypto/md5"
    "encoding/base64"
)

// Implement encryption and decryption through RSA
// Generate private key using RSA method
func main() {
    // RSA first generates the private key, then generates the public key based on the private key
    // Generate 1024-bit private key
    pri, _ := rsa.GenerateKey(rand.Reader, 2048)
    // Generate public key based on private key
    pub := &pri.PublicKey
    // Define plaintext
    plaintext := []byte("hello china")
    // Encrypt to ciphertext, using OAEP padding
    ciphertext, _ := rsa.EncryptOAEP(md5.New(), rand.Reader, pub, plaintext, nil)
    fmt.Println(base64.StdEncoding.EncodeToString(ciphertext))

    // Decrypt
    plaintext, _ = rsa.DecryptOAEP(md5.New(), rand.Reader, pri, ciphertext, nil)
    fmt.Println(string(plaintext))
}

Golang Implementation of DSA Signing and Verification#

package main

import (
    "crypto/dsa"
    "crypto/rand"
    "fmt"
)

// DSA is used for signing and verifying
func main() {
    // DSA is specialized for signing and verification
    var param dsa.Parameters // The structure contains three very large numbers bigInt
    // Instantiate the structure
    dsa.GenerateParameters(&param, rand.Reader, dsa.L1024N160) // L is 1024, N is 160; here L is the private key, N is the public key initial parameter
    // Generate private key
    var priv dsa.PrivateKey // privatekey is a structure that contains the publickey structure, which has a Parameters field
    priv.Parameters = param
    // Generate private key using random read numbers and param
    dsa.GenerateKey(&priv, rand.Reader)

    // Generate public key from private key
    pub := priv.PublicKey
    // Set plaintext
    plaintext := []byte("hello world")
    // Sign the plaintext to obtain two random integers r and s
    r, s, _ := dsa.Sign(rand.Reader, &priv, plaintext) // Use public key to verify the signature, verifying r and s
    b := dsa.Verify(&pub, plaintext, r, s)
    if b == true {
        fmt.Println("Verification successful")
    } else {
        fmt.Println("Verification failed")
    }
}

Hash (Hash) Algorithm#

The hash function is an important branch of cryptography. It is a type of mathematical function that can transform messages of arbitrary length into a fixed-length binary string in a reasonable time, and it is irreversible. This output value is called the hash value, also known as the digest or message digest. Hash algorithms based on hash functions have wide applications in digital signatures, achieving data integrity, Merkle tree data storage and retrieval, and more.

In the Bitcoin system, two cryptographic hash functions are used: one is SHA256, and the other is ripemd160. Ripemd160 is mainly used to generate Bitcoin addresses, while SHA256 is the hash function for almost all cryptographic algorithms on the Bitcoin chain.

  1. Technical Principles

The hash function is also called a hash function or a digest function. It is a one-way cryptographic mechanism, meaning it can only encrypt but cannot decrypt. The mathematical expression can be: h = H(m), where H is the hash function, m is the information to be encrypted, and h is the output fixed-length hash value. The operation process involves setting an initial vector, padding the message to the length required by the algorithm, splitting the padded message into N data blocks, and iteratively processing the N data blocks with the initial vector through the hash algorithm to finally obtain a fixed-length hash value.

The hash function has the following characteristics:

  1. Compression: It encrypts information of arbitrary length into a fixed-length hash value;
  2. One-way: The mathematical principle of the hash function has no inverse operation, so it cannot convert the hash value back into the original information;
  3. Collision resistance: The operation process of the hash function is quite complex, involving various mathematical operations and a large number of variable iterations, making it nearly impossible for two different messages to produce the same hash value;
  4. High sensitivity: Any small input can lead to significant changes in the output.

Typical hash functions include two categories: Message Digest Algorithm (MD5) and Secure Hash Algorithm (SHA).

  1. (SHA).

  2. Hash Collision

An ideal hash function produces two different hash values for different inputs. In practice, if there exist two different messages m and m' such that H(m) = H(m'), then m and m' are said to be a collision for that function. In simple terms, hash collision refers to two different messages producing the same hash value under the action of the same hash function.

To ensure data security and immutability, the actual hash algorithm must be complex enough to have strong collision resistance.
Collision resistance can be divided into two types: weak collision resistance, which means that for a specified message x and function H, it is computationally infeasible to find a message y such that H(x) = H(y); and strong collision resistance, which means that given function H, it is infeasible to find any pair of different messages x and y such that H(x) = H(y).

SHA256

SHA is a family of cryptographic hash functions, which stands for Secure Hash Algorithm. It was designed by the National Security Agency (NSA) and published by the National Institute of Standards and Technology (NIST). The SHA family currently has three series: SHA-1, SHA-2, and SHA-3. Because SHA-1 has been found to be vulnerable to attacks, it is now rarely used. SHA-3, which was produced in 2012, is also known as the Keccak algorithm and is mainly used in the Ethereum public chain. SHA-2 is the most widely used algorithm today, especially in the first generation of public chains like Bitcoin.

The SHA algorithm has the following characteristics: 1. It is not possible to restore information from the message digest; 2. Two different messages will not produce the same message digest.
SHA256 is currently the most basic and widely applied algorithm in blockchain encryption. It is the most representative encryption algorithm in the SHA-2 algorithm series. Understanding and proficiently using SHA256 is the most basic requirement for blockchain technology professionals.

  1. SHA256 Algorithm Principles

SHA-256#

SHA-256 refers to a hashing algorithm that processes messages of any length less than 2^64 bits (calculated in bits) in 512-bit blocks, ultimately producing a 32-byte length data as the encryption algorithm. The generated data is called the message digest. Due to the uniqueness and determinism of the message digest, it can be used to verify whether data has changed during transmission, i.e., to verify its integrity.

image

1.1 Operation Units

The processing unit of the SHA algorithm is bits. In this article, a "word" is 32 bits, while a "byte" is 8 bits. For example, the string "abc" can be converted into a bit string: 01100001 01100010 01100011. It can also be represented as a hexadecimal string: 0x616263.

1.2 Padding

The message is converted into a binary string, and a "1" and several "0"s are added at the end to make its length a multiple of 512, with a remainder of 448. For example, the padding process for the information "abc" is shown below.

Original information: 01100001 01100010 01100011
Padding Step 1: 0110000101100010 01100011 1
First, add a "1"
Padding Step 2: 0110000101100010 01100011 10…..0
Then add 423 "0"s.

1.3 Message Filling

After padding the message, an additional 64-bit message length information is appended to ensure that the padded message length is exactly a multiple of 512 bits. The appended 64-bit message length information is the bit length of the original message, and the padded message will be divided into N 512-bit message blocks.

1.4 Initial Vector

SHA256 is an iterative hash function based on the Merkle-Damgard structure, which requires an initial vector during the first operation. This vector is a variable throughout the operation process. The initial values for SHA256 are derived from the decimal parts of the square roots of the first 8 prime numbers (2, 3, 5, 7, 11, 13, 17, 19). For example, the decimal part of the square root of 2 is: 0.414213562373095048..., which when converted to binary takes the first 32 bits: 10110111111100101010000101001010, and when converted to hexadecimal gives: 0x6a09e667. Similarly, the initial values for the other primes are obtained. These initial values are stored in 8 registers A, B, C, D, E, F, G, and H, which are:

A= H0 = 0x6a09e667
B= H1 = 0xbb67ae85
C= H2 = 0x3c6ef372
D= H3 = 0xa54ff53a
E= H4 = 0x510e527f
F= H5 = 0x9b05688c

G= H6 = 0x1f83d9ab
H= H7 = 0x5be0cd19

1.5 The 64 Constants Used

In the SHA256 algorithm, 64 constants are used, which are derived from the decimal parts of the cubes of the first 64 prime numbers. Their purpose is to provide a set of 64 random strings to be randomly selected as parameters for changing the initial vector function for each message block operation. These 64 constants are as follows:

428a2f98 71374491 b5c0fbcf e9b5dba5 
3956c25b 59f111f1 923f82a4 ab1c5ed5 
d807aa98 12835b01 243185be 550c7dc3 
72be5d74 80deb1fe 9bdc06a7 c19bf174 
e49b69c1 efbe4786 0fc19dc6 240ca1cc 
2de92c6f 4a7484aa 5cb0a9dc 76f988da 
983e5152 a831c66d b00327c8 bf597fc7 
c6e00bf3 d5a79147 06ca6351 14292967 
27b70a85 2e1b2138 4d2c6dfc 53380d13 
650a7354 766a0abb 81c2c92e 92722c85 
a2bfe8a1 a81a664b c24b8b70 c76c51a3 
d192e819 d6990624 f40e3585 106aa070 
19a4c116 1e376c08 2748774c 34b0bcb5 
391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3 
748f82ee 78a5636f 84c87814 8cc70208 
90befffa a4506ceb bef9a3f7 c67178f2

1.6 Operation Process

The operation process is briefly described as follows:

  1. Create 8 variables a, b, c, d, e, f, g, h, and assign them the values of the initial vector;

  2. After padding and filling the original message, divide it into N 512-bit message blocks M(i);

  3. The operation M has a large loop, such as: For i = 1 to N;

  4. Inside the large loop, there is a 64-time loop to change the 8 variables, and the final changed 8 variables are used as parameters for the next large loop;

  5. The final a, b, c, d, e, f, g, h obtained from the large loop are concatenated together to form the final 256-bit message digest.

  6. The specific functions inside the 64-time loop are as follows:
    For t = 0 to 63
    T1 = (h + (∑1(e) + CH(e, f, g) + Kt + Wt) mod 2^32
    T2 = ∑0(a) + MAJ(a, b, c) mod 2^32
    h = g
    g = f
    f = e
    e = (d + T1) mod 2^32
    d = c
    c = b
    b = a
    a = (T1 + T2) mod 2^32

Where ∑1(e) and ∑0(a) are bitwise shift XOR functions of e and a, respectively; MAJ(a, b, c) is the addition function of a, b, and c, and T1 and T2 are two temporary variables generated at each step; Kt is a randomly selected number from the random string; Wt is the processing function for the input message block.

  1. The processing of the message block: Each message block is decomposed into 16 32-bit words, denoted as w[0], …, w[15]. This means that the first 16 words are directly obtained from the first i block of the message, while the remaining words are obtained through the following iterative formula, expressed as Wt = w[t], 0 <= t <= 16
    Wt = σ1(Wt−2) + Wt−7 + σ0(Wt−15) + Wt−16, 16 <= t <= 63
    The eight variables produced in the last loop are combined to form the hash string Hi corresponding to the i block, which is the hash value obtained from SHA256 encryption.

  2. SHA256 Applications and Code Implementation in Blockchain

SHA256 has been proven to be very secure since its inception, and its application in blockchain is the most extensive, such as in the mining algorithm of Bitcoin, generating account addresses, and generating hashes for blocks in Ethereum, etc.
Its usage is very simple, as the SHA256 algorithm has already been encapsulated in the Golang library, so we can directly call the method when using it. The function hash.Sha256 takes a byte slice as its parameter and returns a byte slice as its return value, which should be noted when using it.
The Go code implementation of SHA256 has two methods, both of which have the same principle and output:

package main

import (
    "github.com/nebulasio/go-nebulas/crypto/hash"
    "fmt"
    "encoding/hex"
    "crypto/sha256"
)

func main() {

    a := "helloworld"

    // Method 1: Direct output with one method
    hash := hash.Sha256([]byte(a))
    fmt.Println(hex.EncodeToString(hash))

    sha256.New()

    // Method 2: Step-by-step output
    h := sha256.New() // Create SHA256 algorithm
    h.Write([]byte(a)) // Encrypt the parameter a using the SHA256 algorithm to obtain 8 variables
    hash1 := h.Sum(nil) // Add the 8 variables to obtain the final hash

    fmt.Println(hex.EncodeToString(hash1))
}

// Output
// 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af
// 936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af

Digital Signatures#

Digital signatures play an important role in information security, including identity authentication, data integrity, non-repudiation, and anonymity. They are an important branch of modern cryptography. The signing process involves the sender using their private key to perform a so-called encryption operation on the sent information, resulting in a hash value, which is the signature. The signature and information are sent to the recipient. The recipient uses the sender's public key and the received information to verify the signature, confirming that the received information is complete and accurate; otherwise, it indicates that the message source is incorrect.

In simple terms, digital signatures involve signing with a private key and verifying with a public key.

  1. Ordinary Signatures

A signature is simply made with one private key, and the signing action is performed by the sender themselves; this type of signature is an ordinary signature. There are many commonly used signing methods, including RSA digital signatures, DSS digital signatures, ElGamal digital signatures, ECDSA digital signatures, etc. Among them, RSA and ECDSA signatures have been discussed in the encryption algorithms section. The most commonly used signature method in blockchain projects is the ECDSA digital signature. The principles of signing and verification are not elaborated here; interested readers can refer to previous chapters.

Since ECDSA digital signatures are crucial in blockchain, I will next explain how to apply them.
The following code demonstrates how to use ECDSA to sign data BLOCK before it is added to the chain, requiring verification before it can be added. Specific explanations are provided in the code.

Process:

  1. Generate a private key using ecdsa.GenerateKey;
    The output private key is of pointer type;
  2. Generate a public key from the private key;
  3. Perform a hash operation on the data BLOCK, which in practice is the mining process;
  4. To allow any length of data to be signed, we create our own signing method;
  5. Verify whether the data is legitimate, meaning the signing can only be performed if the verification is successful.
package main

import (
    "bytes"
    "encoding/binary"
    "log"
    "time"
    "crypto/sha256"
    "fmt"
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "math/big"
)

// Simple blockchain block structure
type Block struct {
    // 1. Block height
    Height int64
    // 2. Previous block hash
    PrevBlockHash []byte
    // 3. Transaction data
    Data []byte
    // 4. Timestamp
    Timestamp int64
    // 5. Actual hash obtained through mining
    Hash []byte
    // 6. Random number Nonce
    Nonce int64
}

func main() {
    // Call the underlying function to generate a private key
    prk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

    // Generate
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.