Java 블록체인 구현 가이드 – 해시부터 채굴까지 단계별 코드로 배우기


블록체인이라는 단어는 들어봤지만, 실제로 어떻게 동작하는지 코드로 확인해본 적 있으신가요? Java 블록체인 구현은 암호화 기초부터 분산 원장의 핵심 원리까지 직접 손으로 짚어볼 수 있는 최고의 실습입니다. 이론만으로는 느껴지지 않던 블록체인의 구조가, 코드 한 줄 한 줄을 작성하는 순간 명확하게 눈에 들어옵니다. 이 글에서는 자바만 있으면 누구나 따라 만들 수 있는 미니 블록체인을 단계별로 함께 구현해봅니다.


목차

  1. 블록체인이란 무엇인가 – 핵심 개념 정리
  2. 블록체인의 핵심 원리 – 해시와 체인 연결 메커니즘
  3. Java로 블록 클래스 구현하기
  4. Proof of Work – 채굴과 난이도 조절 구현
  5. 체인 무결성 검증과 주의해야 할 함정들
  6. 전체 코드 완성 및 확장 방향

1. 블록체인이란 무엇인가 – 핵심 개념 정리

블록체인을 한 마디로 정의하면 “변조가 불가능한 분산 거래 장부” 입니다. 이름 그대로 ‘블록(Block)’들이 ‘체인(Chain)’처럼 연결된 구조입니다. 마치 고리가 연결된 자전거 체인처럼, 하나의 고리를 끊으면 전체 체인이 망가지는 방식으로 데이터 무결성을 보장합니다.

블록 하나에는 무엇이 담길까?

각각의 블록은 크게 세 가지 요소를 포함합니다.

구성 요소설명
데이터(Data)거래 정보, 기록 내용 등 실제 저장할 내용
해시(Hash)이 블록의 고유 지문. 내용이 바뀌면 해시도 바뀜
이전 블록의 해시앞 블록과 연결되는 연결고리

이 세 가지가 체인처럼 연결되면, 중간 어느 블록이라도 내용을 바꾸는 순간 그 블록의 해시값이 달라지고, 뒤에 이어진 모든 블록이 “앞 블록 해시가 틀렸다”고 감지하게 됩니다. 이것이 블록체인이 위변조에 강한 핵심 이유입니다.

제네시스 블록이란?

블록체인의 가장 첫 번째 블록을 **제네시스 블록(Genesis Block)**이라고 부릅니다. 이 블록은 이전 블록이 없으므로, 이전 해시값을 "0" 또는 "GENESIS" 같은 고정 값으로 설정합니다. 비트코인의 제네시스 블록은 2009년 1월 3일 사토시 나카모토가 생성했으며, 그 안에 당시 신문 헤드라인을 넣어두었다는 유명한 일화가 있습니다. 우리도 이 글에서 제네시스 블록을 직접 만들어볼 것입니다.

블록체인의 개념이 복잡해 보여도, 결국은 “데이터 + 고유 지문 + 이전 지문” 이라는 세 가지 규칙만 지키면 됩니다. 이 단순한 규칙이 만들어내는 신뢰가 수백조 원 규모의 암호화폐 시장을 떠받치고 있습니다.


2. 블록체인의 핵심 원리 – 해시와 체인 연결 메커니즘

블록체인의 모든 보안은 해시 함수(Hash Function) 에서 시작합니다. 해시 함수란 어떤 길이의 입력값이든 받아서 고정된 길이의 고유한 문자열로 변환해주는 수학 함수입니다. 요리에 비유하면, 재료(입력값)가 무엇이든 믹서기(해시 함수)에 넣으면 항상 같은 크기의 스무디(해시값)가 나오는 것과 같습니다. 단, 스무디를 다시 원재료로 되돌릴 수는 없습니다.

SHA-256이 블록체인에 쓰이는 이유

비트코인을 포함한 대부분의 블록체인은 SHA-256(Secure Hash Algorithm 256-bit) 을 사용합니다. Java에서는 java.security.MessageDigest 클래스로 간단히 구현할 수 있습니다.

SHA-256이 선택된 이유는 세 가지 핵심 특성 때문입니다.

  • 결정론적(Deterministic): 같은 입력은 항상 같은 해시를 출력
  • 눈사태 효과(Avalanche Effect): 입력값 한 글자만 바꿔도 해시값이 완전히 달라짐
  • 역방향 불가능(Pre-image Resistance): 해시값으로 원본 데이터를 복원할 수 없음

java

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashUtil {

    /**
     * 문자열을 입력받아 SHA-256 해시값(16진수)을 반환
     */
    public static String sha256(String input) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hashBytes = digest.digest(input.getBytes("UTF-8"));

            // byte 배열을 16진수 문자열로 변환
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
            return hexString.toString();

        } catch (Exception e) {
            throw new RuntimeException("해시 계산 중 오류 발생", e);
        }
    }
}

체인 연결 구조 다이어그램

아래 ASCII 다이어그램은 블록들이 어떻게 해시로 연결되는지를 보여줍니다.

┌─────────────────────────┐
│      블록 #0 (제네시스)    │
│  previousHash: "0"      │
│  data: "Genesis Block"  │
│  hash: "0000a1b2c3..."  │
└──────────┬──────────────┘
           │ hash 전달
           ▼
┌─────────────────────────┐
│         블록 #1          │
│  previousHash: "0000a1b2c3..."  │
│  data: "Alice→Bob: 5BTC"│
│  hash: "0000d4e5f6..."  │
└──────────┬──────────────┘
           │ hash 전달
           ▼
┌─────────────────────────┐
│         블록 #2          │
│  previousHash: "0000d4e5f6..."  │
│  data: "Bob→Carol: 3BTC"│
│  hash: "0000g7h8i9..."  │
└─────────────────────────┘

이 구조에서 블록 #1의 데이터를 수정하면, #1의 해시가 바뀌고, #2의 previousHash와 불일치가 발생합니다. 이것이 체인 전체의 무결성을 보장하는 핵심 메커니즘입니다.


3. Java로 블록 클래스 구현하기

이제 실제 코드를 작성할 시간입니다. Java 블록체인 구현의 첫 번째 단계는 Block 클래스를 만드는 것입니다. 이 클래스 하나가 블록체인의 기본 단위가 됩니다.

Block 클래스 전체 코드

java

public class Block {

    public String hash;           // 이 블록의 해시값
    public String previousHash;   // 이전 블록의 해시값
    public String data;           // 블록에 저장할 데이터 (거래 내역 등)
    public long timeStamp;        // 블록 생성 시각 (Unix 타임스탬프)
    public int nonce;             // Proof of Work에서 사용할 임시값

    /**
     * 생성자: 이전 해시와 데이터를 받아 블록을 초기화하고 해시를 계산
     */
    public Block(String previousHash, String data) {
        this.previousHash = previousHash;
        this.data = data;
        this.timeStamp = System.currentTimeMillis();
        this.nonce = 0;
        this.hash = calculateHash(); // 생성 즉시 해시 계산
    }

    /**
     * 이 블록의 해시값을 계산 (이전해시 + 타임스탬프 + nonce + 데이터 조합)
     */
    public String calculateHash() {
        String input = previousHash
                + Long.toString(timeStamp)
                + Integer.toString(nonce)
                + data;
        return HashUtil.sha256(input);
    }

    @Override
    public String toString() {
        return String.format(
            "[블록]\n  데이터: %s\n  이전해시: %s\n  해시: %s\n  nonce: %d\n",
            data, previousHash, hash, nonce
        );
    }
}

Blockchain 클래스 – 체인 관리자

Block을 담을 체인 전체를 관리하는 Blockchain 클래스도 만들어봅니다.

java

import java.util.ArrayList;
import java.util.List;

public class Blockchain {

    private List<Block> chain = new ArrayList<>();
    private int difficulty = 4; // 채굴 난이도 (앞에 0이 몇 개 와야 하는지)

    /**
     * 생성자: 제네시스 블록을 자동으로 생성
     */
    public Blockchain() {
        chain.add(createGenesisBlock());
    }

    /**
     * 제네시스 블록 생성 (이전 해시 없음)
     */
    private Block createGenesisBlock() {
        Block genesis = new Block("0", "Genesis Block");
        genesis.mineBlock(difficulty);
        return genesis;
    }

    /**
     * 새 블록을 채굴하여 체인에 추가
     */
    public void addBlock(String data) {
        Block lastBlock = chain.get(chain.size() - 1);
        Block newBlock = new Block(lastBlock.hash, data);
        newBlock.mineBlock(difficulty);
        chain.add(newBlock);
        System.out.println("✅ 블록 추가 완료: " + newBlock.data);
    }

    /**
     * 블록 목록 반환
     */
    public List<Block> getChain() {
        return chain;
    }
}

이 두 클래스만으로 블록체인의 골격이 완성됩니다. Block은 단위 데이터 컨테이너이고, Blockchain은 그 블록들의 목록을 관리하는 컨트롤러 역할을 합니다.


4. Proof of Work – 채굴과 난이도 조절 구현

블록체인에서 **채굴(Mining)**이란 새로운 블록을 체인에 추가하기 위해 특정 조건을 만족하는 해시값을 찾는 과정입니다. 단순히 데이터를 저장하는 것이 아니라, 일정 이상의 계산 작업(Proof of Work)을 증명해야만 블록 추가가 허용됩니다.

왜 이렇게 복잡하게 할까요? 공격자가 블록을 마음대로 수정하려면 해당 블록부터 이후 모든 블록을 다시 채굴해야 하는데, 이 계산 비용이 너무 크기 때문에 현실적으로 불가능해집니다. 이것이 블록체인 보안의 경제적 근거입니다.

mineBlock 메서드 구현

java

/**
 * 난이도(difficulty)에 맞는 해시값이 나올 때까지 nonce를 증가시키며 반복
 * 예: difficulty=4이면 해시값이 "0000"으로 시작해야 함
 */
public void mineBlock(int difficulty) {
    // 목표 접두사: difficulty 개수만큼 '0' 반복
    String target = "0".repeat(difficulty);

    long startTime = System.currentTimeMillis();

    while (!hash.startsWith(target)) {
        nonce++;                    // nonce 1 증가
        hash = calculateHash();     // 새 해시 계산
    }

    long elapsedTime = System.currentTimeMillis() - startTime;
    System.out.printf("⛏ 채굴 완료! nonce: %d | 소요 시간: %dms | 해시: %s%n",
                      nonce, elapsedTime, hash);
}

난이도에 따른 채굴 시간 비교

난이도가 올라갈수록 채굴 시간이 기하급수적으로 늘어납니다. 아래는 일반적인 노트북에서의 대략적인 소요 시간입니다.

난이도목표 접두사평균 채굴 시간
101ms 미만
200~5ms
3000~50ms
40000~500ms
500000~5,000ms

실제 비트코인은 난이도가 훨씬 높고, 전 세계 수백만 대의 전용 채굴 장비(ASIC)가 동시에 경쟁합니다. 우리의 미니 블록체인은 difficulty = 4 정도가 실습하기에 적당합니다.


5. 체인 무결성 검증과 주의해야 할 함정들

블록체인을 구현할 때 가장 중요하지만 놓치기 쉬운 부분이 바로 무결성 검증(Validation) 입니다. 체인이 아무리 길어도, 중간 어딘가가 변조되었다면 즉시 감지할 수 있어야 합니다.

체인 검증 메서드 구현

java

/**
 * 체인 전체의 무결성을 검증
 * 1) 각 블록의 해시가 재계산값과 일치하는지 확인
 * 2) 각 블록의 previousHash가 이전 블록의 hash와 일치하는지 확인
 */
public boolean isChainValid() {
    for (int i = 1; i < chain.size(); i++) {
        Block current = chain.get(i);
        Block previous = chain.get(i - 1);

        // 현재 블록의 해시가 유효한지 검사
        if (!current.hash.equals(current.calculateHash())) {
            System.out.println("❌ 블록 #" + i + " 해시 불일치 – 데이터 변조 감지!");
            return false;
        }

        // 이전 블록과의 연결이 끊어지지 않았는지 검사
        if (!current.previousHash.equals(previous.hash)) {
            System.out.println("❌ 블록 #" + i + " 이전 해시 불일치 – 체인 연결 오류!");
            return false;
        }
    }
    System.out.println("✅ 체인 무결성 검증 통과!");
    return true;
}

초보자가 자주 저지르는 3가지 실수

실수 1 – 타임스탬프를 해시에 포함하지 않는 경우 타임스탬프를 해시 계산에서 제외하면, 같은 데이터를 가진 블록이 항상 동일한 해시를 생성합니다. 이 경우 해시만 미리 계산해두는 공격(Pre-computation Attack)에 취약해집니다. 반드시 timeStampcalculateHash() 입력에 포함시키세요.

실수 2 – nonce를 초기화하지 않는 경우 블록을 재생성하거나 재검증할 때 nonce를 초기화하지 않으면, 이전 채굴에서 사용된 nonce값이 남아 해시가 틀어집니다. 생성자에서 반드시 this.nonce = 0으로 초기화하세요.

실수 3 – 검증 시 calculateHash()를 다시 호출하지 않는 경우 저장된 hash 필드만 비교하면 누군가 hash 필드 자체를 바꿔치기했을 때 감지하지 못합니다. isChainValid()에서는 반드시 calculateHash()를 다시 호출하여 재계산된 값과 비교해야 합니다.


6. 전체 코드 완성 및 확장 방향

이제 지금까지 만든 모든 클래스를 Main.java에서 실행해봅니다.

Main.java – 전체 실행 코드

java

public class Main {

    public static void main(String[] args) {

        System.out.println("=== Java 블록체인 시작 ===\n");

        // 블록체인 생성 (제네시스 블록 자동 생성)
        Blockchain blockchain = new Blockchain();

        // 블록 추가
        blockchain.addBlock("Alice → Bob: 10 BTC");
        blockchain.addBlock("Bob → Carol: 5 BTC");
        blockchain.addBlock("Carol → Dave: 2 BTC");

        System.out.println("\n=== 체인 출력 ===");
        for (Block block : blockchain.getChain()) {
            System.out.println(block);
        }

        System.out.println("\n=== 무결성 검증 ===");
        System.out.println("체인 유효: " + blockchain.isChainValid());

        // --- 변조 시뮬레이션 ---
        System.out.println("\n=== 변조 시도 (블록 #1 데이터 조작) ===");
        blockchain.getChain().get(1).data = "Alice → Hacker: 10 BTC";
        // 해시는 업데이트하지 않음 → 불일치 발생

        System.out.println("변조 후 체인 유효: " + blockchain.isChainValid());
    }
}

실행 결과 예시:

=== Java 블록체인 시작 ===

⛏ 채굴 완료! nonce: 8243 | 소요 시간: 312ms | 해시: 0000a3f1...
✅ 블록 추가 완료: Alice → Bob: 10 BTC
⛏ 채굴 완료! nonce: 14852 | 소요 시간: 487ms | 해시: 00004c2b...
✅ 블록 추가 완료: Bob → Carol: 5 BTC
...
✅ 체인 무결성 검증 통과!

=== 변조 시도 (블록 #1 데이터 조작) ===
❌ 블록 #1 해시 불일치 – 데이터 변조 감지!
변조 후 체인 유효: false

이 미니 블록체인을 더 발전시키려면?

지금 구현한 블록체인은 단일 노드에서 동작하는 중앙집중식 구조입니다. 실제 블록체인과 가까워지려면 아래 방향으로 확장할 수 있습니다.

확장 기능설명난이도
P2P 네트워크여러 노드가 동일한 체인을 공유⭐⭐⭐
트랜잭션 모델거래 서명, 잔액 검증 추가⭐⭐⭐
머클 트리거래 묶음의 효율적 검증⭐⭐⭐⭐
REST APIHTTP로 블록 추가/조회 기능⭐⭐
월렛 구현공개키/개인키 기반 주소 생성⭐⭐⭐⭐

결론

Java 블록체인 구현은 해시 함수, 체인 연결, Proof of Work라는 세 가지 개념으로 완성됩니다. 복잡해 보이는 블록체인도 결국은 “이전 블록의 지문을 품고 있는 데이터 묶음”이며, 이것을 Java 코드로 직접 작성하면 그 원리가 명확하게 체화됩니다. 오늘 만든 미니 블록체인을 기반으로 P2P 네트워크나 트랜잭션 모델로 확장해보세요. 그 과정이 진짜 블록체인 개발자로 가는 가장 빠른 길입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다