讀完了 <區塊鍊 上司幹部讀本> , 以及看了一部分<區塊鍊原理,設計與應用>,
通過對區塊鍊的一些了解跟随網絡部落格完成demo
建立區塊鍊
區塊鍊就是一串或者是一系列區塊的集合,類似于連結清單的概念,每個區塊都指向于後面一個區塊,然後順序的連接配接在一起. 那麼每個區塊中内容是? 區塊鍊中的每一個區塊都存放了很多有價值的資訊, 隻要包括3個部分 : 自己的數字簽名, 上一個區塊的數字簽名, 還有一切需要加密的資料 ( 這些資料在比特币中就相當于是交易的資訊,它是加密貨币的本質). 每個數字簽名不但證明了自己是特有的一個區塊, 而且指向了前一個區塊的來源,讓所有的區塊在鍊條中可以串起來,而資料就是一些特定的資訊, 你可以按照業務邏輯來儲存業務資料.
block
這裡的hash指的就是數字簽名
是以每一個區塊不僅包含前一個區塊的hash值, 同時包含自身的一個hash值, 自身的hash值是通過之前的hash值和資料data通過hash計算出來的. 如果前一個區塊的資料一旦被篡改了, 那麼前一個區塊的hash值也會同樣發生變化 (因為資料也被計算在内) ,這樣也就導緻了所有後續的區塊中的hash值, 是以計算和對比hash值會讓我們檢查到目前的區塊鍊是否是有效的, 也就避免了資料被惡意篡改的可能性, 因為篡改資料就會改變hash值并破壞整個區塊鍊.
定義區塊鍊的類Block:
package com.sha256.sha256.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
Data
@NoArgsConstructor
@AllArgsConstructor
public class Block {
private String hash; // our signature
private String previousHash; // the hash of previous block
private String data; //our data will be a simple message.
private long timeStamp; //as number of milliseconds since 1/1/1970.
//Block Constructor
public Block(String data,String previousHash){
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
}
}
String hash是我們的數字簽名, 變量previousHash儲存前一個區塊的hash值, String data是儲存我們區塊的資料(比如交易轉賬資訊).
建立數字簽名
熟悉加密算法的朋友,Java方式可以實作的加密方式很多, 例如BASE, MD, RSA ,SHA 等等, 我在這裡選用了SHA256這種加密方式, SHA ( Secure Hash Algorithm ) 安全雜湊演算法, 這種算法的特點是資料的少量更改會在Hash值中産生不可預知的大量更改, hash值用作表示大量資料的固定大小的唯一值, 而SHA256算法的hash值大小為256位. 之是以選用SHA256是因為它的大小正合适, 一方面産生重複hash值的可能性很小, 另一方面在區塊鍊實際應用過程中, 有可能會産生大量的區塊, 而使得資訊量很大, 那麼256位的大小就比較恰當了.
密碼學與安全技術
節選<區塊鍊原理,設計與應用>
測試SHA256加密:
package com.sha256.sha256.test;
import com.sha256.sha256.utils.SHA256Util;
public class TestSHA256 {
public static void main(String[] args) {
String message0 = "我是要被加密的資訊";
String message1 = "我是要被加密的資訊";
String message2 = "我是要被加密的資訊.";
String encryptionMessage0 = SHA256Util.applySha256(message0);
String encryptionMessage1 = SHA256Util.applySha256(message1);
String encryptionMessage2 = SHA256Util.applySha256(message2);
System.out.println(encryptionMessage0);
System.out.println(encryptionMessage1);
System.out.println(encryptionMessage2);
}
}
輸出:
2d7641299aba44f11e8b567dc55f9a45c5218e20bdb65d1306020bfb09fe2f31
2a6588b9fd3b412176b4cf499c23f1aa06b35843e6082ca0ab2227f4129bc805
Hash算法與數字摘要:
Hash定義:
Hash(哈希或散列)算法是非常基礎也非常重要的計算機算法,它能将任意長度的二進制明文串映射為較短的(通常是固定長度的)二進制串(Hash值), 并且不同的明文很難映射為相同的Hash值.
這意味着對于某個檔案,無需檢視其内容,隻要其SHA-256 Hash計算後結果同樣為:
2d7641299aba44f11e8b567dc55f9a45c5218e20bdb65d1306020bfb09fe2f31,
則說明檔案内容極大機率上就是 –> 我是要被加密的資訊 幾個字.
Hash值在應用中又常被稱為指紋(fingerprint)或摘要(digest). Hash算法的核心思想也經常被應用到基于内容的編址或命名算法中.
一個優秀的Hash算法将能實作如下功能:
正向快速 : 給定明文和Hash算法, 在有限時間和有限資源内能計算得到Hash值.
逆向困難 : 給定(若幹)Hash值, 在有限時間内很難(基本不可能)逆推出明文;
輸入敏感 : 原始輸入資訊發生任何改變,新産生的Hahs值都應該出現很大不同;(見上面的三個字元串的比較)
沖突避免 : 很難找到兩端内容不同的明文,使得它們的Hash值一緻(發生碰撞).
沖突避免有時候又稱為”抗碰撞性”, 分為”弱抗碰撞性”和”強抗碰撞性”. 如果給定明文前提下, 無法找到與之碰撞的其他明文, 則算法具有”弱抗碰撞性”, 如果無法找到任意兩個發生Hash碰撞的明文, 則稱算法具有”強抗碰撞性”.
很多場景下,也往往要求算法對于任意長的輸入内容,可以輸入定長的Hash值結果.
常見算法
Hash算法和MD5等
加密安全
提示: MD5是一個經典的Hash算法,和SHA-1算法一起都被認為安全性已不足應用于商業場景.
調用工具類的SHA256算法:
package com.sha256.sha256.utils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256Util {
//Applies SHA256 to a string and returns the result
//SHA256 encryption
public static String applySha256(String input){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
//Applies sha256 to our input
byte[] hash = digest.digest(input.getBytes("UTF-8"));
//This will contain hash as hexidecimal
StringBuffer hexString = new StringBuffer();
for(int i=0;i<hash.length;i++){
String hex = Integer.toHexString(0xff & hash[i]);
if(hex.length()==1){
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
}
強化Block實體類:
對hash值進行指派:
package com.sha256.sha256.bean;
import com.sha256.sha256.utils.SHA256Util;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Block {
private String hash; // our signature
private String previousHash; // the hash of previous block
private String data; //our data will be a simple message.
private long timeStamp; //as number of milliseconds since 1/1/1970.
//Block Constructor
public Block(String data,String previousHash){
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = SHA256Util.calculateHash(this); //Making sure we do this after we set the other values.
}
}
同時在工具類中:
加入新的方法 calculateHash:
package com.sha256.sha256.utils;
import com.sha256.sha256.bean.Block;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256Util {
//Applies SHA256 to a string and returns the result
//SHA256 encryption
public static String applySha256(String input){
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
//Applies sha256 to our input
byte[] hash = digest.digest(input.getBytes("UTF-8"));
//This will contain hash as hexidecimal
StringBuffer hexString = new StringBuffer();
for(int i=0;i<hash.length;i++){
String hex = Integer.toHexString(0xff & hash[i]);
if(hex.length()==1){
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
//calculate the hash use previoushash , timestamp , data
public static String calculateHash(Block block){
String calculateHash = SHA256Util.applySha256(block.getPreviousHash() + Long.toString(block.getTimeStamp()) + block.getData());
return calculateHash;
}
}
測試:
package com.sha256.sha256.test;
import com.sha256.sha256.bean.Block;
import com.sha256.sha256.utils.SHA256Util;
public class TestSHA256 {
public static void main(String[] args) {
//test1 測試三個被加密字元串 加密後的hash值的差别
/**
* 雖然第三條資訊僅僅多一個".",但加密後的資料hash相差極大
*/
String message0 = "我是要被加密的資訊";
String message1 = "我是要被加密的資訊";
String message2 = "我是要被加密的資訊.";
String encryptionMessage0 = SHA256Util.applySha256(message0);
String encryptionMessage1 = SHA256Util.applySha256(message1);
String encryptionMessage2 = SHA256Util.applySha256(message2);
System.out.println(encryptionMessage0);
System.out.println(encryptionMessage1);
System.out.println(encryptionMessage2);
//建立區塊鍊邏輯, 因為第一個塊沒有上一個塊的hash頭部值,是以輸入0 作為前一個塊的previous hash
/**
* 由于在{@link SHA256Util#calculateHash(Block)}
* 中對同時産生的new Date().getTime() (timestamp)
* 也加入進行了hash加密,是以固有的message (data)及
* previoushash之和進行了加密.
*/
Block genesisBlock = new Block("這是第一個區塊中的要被加密的資訊和交易資訊","0");
String hash1 = genesisBlock.getHash();
System.out.println("Hash for block 1 : "+hash1);
Block secondBlock = new Block("這是第二個區塊,以及其中資訊!!!它的前區塊頭部hash我們拿上一個的來使用",hash1);
String hash2 = secondBlock.getHash(); //
System.out.println("Hash for block 2 : "+hash2);
Block thirdBlock = new Block("這是第三個區塊,它的hash應該已經被前兩個的資訊納入進來了,它的hash如果對不上,那麼說明前面的資訊被改動過了",hash2);
String hash3 = thirdBlock.getHash();
System.out.println("Hash for block 3 : "+hash3);
}
}
運作結果:
- 由于在{@link SHA256Util#calculateHash(Block)}
- 中對同時産生的new Date().getTime() (timestamp)
- 也加入進行了hash加密,是以固有的message (data)及
- previoushash之和進行了加密.
Hash for block 1 : cdb1bb85e8f2394f3cee57d82800f5413848fa6c981fefa0fd204497f853c8b4
Hash for block 2 : fad4bc33a9b9f5fc5053fe3583b6bf366be9ea518936ce37d58b916e2c4699be
Hash for block 3 : 558ff9aac60aea20da1936a78a863195cbe23748f08fa34219bb3abc66078b65
注意: 每次 Hash for block * 的産生的值是不同的,因為每次對timestamp進行了計算
每一個區塊都必須要有自己的資料簽名即hash值,這個hash值依賴于自身的資訊(data)和上一個區塊的數字簽名(previousHash), 但這個還不是區塊鍊, 下面讓我們存儲區塊到數組中, 這裡我會引入gson包, 目的是可以用json方式檢視整個一條區塊鍊結構.
看test3
package com.sha256.sha256.test;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.GsonBuilder;
import com.sha256.sha256.bean.Block;
import com.sha256.sha256.utils.SHA256Util;
import java.util.ArrayList;
public class TestSHA256 {
//聲明一個區塊鍊,用于添加Block實體
public static ArrayList<Block> blockChain = new ArrayList<>();
public static void main(String[] args) {
//test1 測試三個被加密字元串 加密後的hash值的差别
/**
* 雖然第三條資訊僅僅多一個".",但加密後的資料hash相差極大
*/
String message0 = "我是要被加密的資訊";
String message1 = "我是要被加密的資訊";
String message2 = "我是要被加密的資訊.";
String encryptionMessage0 = SHA256Util.applySha256(message0);
String encryptionMessage1 = SHA256Util.applySha256(message1);
String encryptionMessage2 = SHA256Util.applySha256(message2);
System.out.println(encryptionMessage0);
System.out.println(encryptionMessage1);
System.out.println(encryptionMessage2);
//test2 建立區塊鍊邏輯, 因為第一個塊沒有上一個塊的hash頭部值,是以輸入0 作為前一個塊的previous hash
/**
* 由于在{@link SHA256Util#calculateHash(Block)}
* 中對同時産生的new Date().getTime() (timestamp)
* 也加入進行了hash加密,是以固有的message (data)及
* previoushash之和進行了加密.
*/
Block genesisBlock = new Block("這是第一個區塊中的要被加密的資訊和交易資訊","0");
String hash1 = genesisBlock.getHash();
System.out.println("Hash for block 1 : "+hash1);
Block secondBlock = new Block("這是第二個區塊,以及其中資訊!!!它的前區塊頭部hash我們拿上一個的來使用",hash1);
String hash2 = secondBlock.getHash(); //
System.out.println("Hash for block 2 : "+hash2);
Block thirdBlock = new Block("這是第三個區塊,它的hash應該已經被前兩個的資訊納入進來了,它的hash如果對不上,那麼說明前面的資訊被改動過了",hash2);
String hash3 = thirdBlock.getHash();
System.out.println("Hash for block 3 : "+hash3);
//test3 add our blocks to the blockchain ArrayList :
blockChain.add(new Block("區塊鍊上第一小節","0"));
blockChain.add(new Block("區塊鍊第二小節",blockChain.get(blockChain.size()-1).getHash()));
blockChain.add(new Block("區塊鍊第三小節",blockChain.get(blockChain.size()-1).getHash()));
// JSONArray blockChainJson1 = (JSONArray)JSONArray.toJSON(blockChain); //JSONArray是不排版的
// System.out.println(blockChainJson1);
String blockChainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockChain);
System.out.println(blockChainJson);
}
}