Python手写区块链时间胶囊:加密存证与定时解锁实战
简介这是一份面向Python开发者与区块链初学者的实战项目源码围绕「区块链上的时间胶囊」展开帮助读者理解如何用Python与智能合约实现信息定时封存与不可篡改存证。资源包共24个文件约159KB以JavaScript、Vue组件、Solidity合约及JSON配置为主另含HTML入口、图标与说明文档覆盖前端界面、合约部署脚本与项目配置等模块。项目结构包含合约层、迁移脚本、前端视图与路由等部分涉及区块链连接、时间戳创建、数据加密、智能合约解锁规则及命令行或Web交互等核心功能便于读者对照源码梳理去中心化应用的完整链路。目前已有203人学习适合希望从零理解区块链时间戳服务、提升分布式系统与信息安全实践能力的开发者参考。1. 用 Python 把一段话锁进区块链这个时间胶囊项目到底能干什么你有没有想过把一段话、一张图片的哈希、甚至一份合同摘要封存在一个“未来才能打开”的盒子里不是网盘加密不是定时邮件而是写进区块链——一旦上链谁都改不了不到时间谁也读不到。这就是我最近拆的一个 Python 小项目区块链上的时间胶囊。它用 Python 从零实现一条极简链把加密后的内容打包成交易通过工作量证明“封印”再靠区块高度或时间戳控制解锁。适合想搞懂区块链底层逻辑、又不想啃 Go 或 Rust 的 Python 开发者。你不需要懂共识算法只要会写函数和类就能跟着把整条链跑起来。2. 拆开时间胶囊区块、交易与加密封印怎么用 Python 拼出来2.1 为什么不用现成链偏要手写一个很多人第一反应是直接调以太坊合约不就行了但时间胶囊这个场景有个特殊点——你不需要代币不需要 gas不需要虚拟机。你要的只是“不可篡改 定时解锁”。手写一条 Python 链反而能把黑匣子拆干净区块结构长什么样、哈希怎么串、工作量证明到底在算啥全在眼皮底下。常见做法是定义一个Block类里面放index、timestamp、data、previous_hash、nonce。data不是明文而是 AES 加密后的密文加上盐值。这样即使别人拿到链上数据没有密钥也读不出内容。链本身只负责存证和排序不负责保密——保密交给对称加密。选型上我一般会推荐hashlibjsontime这三个标准库起步不引入 web3.py 或 bitcoinlib。原因很简单依赖越少你越清楚每一步在干什么。等到逻辑跑通再换成 Flask 暴露 API 也不迟。2.2 区块结构定义与创世块生成先看核心数据结构。下面这段代码定义了一个最小可用的区块并生成创世块。注意data字段存的是加密后的字符串不是原始明文。import hashlib import json import time class Block: def __init__(self, index, timestamp, data, previous_hash, nonce0): self.index index self.timestamp timestamp self.data data # 加密后的密文 self.previous_hash previous_hash self.nonce nonce self.hash self.compute_hash() def compute_hash(self): block_string json.dumps({ index: self.index, timestamp: self.timestamp, data: self.data, previous_hash: self.previous_hash, nonce: self.nonce }, sort_keysTrue) return hashlib.sha256(block_string.encode()).hexdigest() def create_genesis_block(): return Block(0, time.time(), Genesis Block, 0)逻辑说明compute_hash把区块所有字段按固定顺序序列化再取 SHA-256。sort_keysTrue保证不同机器上序列化结果一致否则哈希会对不上。nonce初始为 0后面挖矿时会递增。创世块的previous_hash固定为0它是整条链的锚点。参数说明index是区块高度从 0 开始timestamp用 Unix 时间戳方便比较data建议限制在 1KB 以内否则哈希计算会变慢链体积也会膨胀。2.3 工作量证明把“封印”变成算力承诺时间胶囊的“锁”不是密码锁而是工作量证明。你算出一个满足条件的哈希就等于给这个区块盖了时间戳。篡改它需要重算后面所有区块成本极高。class Blockchain: def __init__(self, difficulty4): self.chain [create_genesis_block()] self.difficulty difficulty # 前导零个数 def proof_of_work(self, block): block.nonce 0 computed_hash block.compute_hash() while not computed_hash.startswith(0 * self.difficulty): block.nonce 1 computed_hash block.compute_hash() return computed_hash def add_block(self, block, proof): previous_hash self.chain[-1].hash if previous_hash ! block.previous_hash: return False if not self.is_valid_proof(block, proof): return False block.hash proof self.chain.append(block) return True def is_valid_proof(self, block, block_hash): return (block_hash.startswith(0 * self.difficulty) and block_hash block.compute_hash())逻辑说明proof_of_work不断递增nonce直到哈希以difficulty个零开头。add_block先校验前向哈希是否匹配再校验工作量证明是否有效两者都通过才追加到链上。is_valid_proof做了双重检查前缀零 哈希可复现防止有人伪造一个哈希但没真正算过。参数说明difficulty4意味着平均要算 16^4 65536 次才能出一个块在笔记本上大约 0.52 秒。调到 5 会慢 16 倍调到 3 则太快失去“封印”意义。我一般建议开发阶段用 3演示用 4真实存证用 5 以上。2.4 加密与解密让内容在链上“看不见”链只存密文密钥由你自己保管。常见做法是用cryptography库的 Fernet 对称加密简单且带完整性校验。from cryptography.fernet import Fernet def generate_key(): return Fernet.generate_key() def encrypt_message(key, message): f Fernet(key) return f.encrypt(message.encode()).decode() def decrypt_message(key, token): f Fernet(key) return f.decrypt(token.encode()).decode()逻辑说明generate_key生成一个 base64 编码的 32 字节密钥必须离线保存。encrypt_message返回的是包含时间戳和 HMAC 的 token直接存进区块的data字段。decrypt_message在解锁时调用如果密钥错误或数据被篡改会抛出InvalidToken异常。参数说明Fernet 的 token 长度随明文增加但通常几百字节完全能塞进一个区块。密钥不要写进代码用环境变量或单独文件加载。我习惯把密钥打印一次后抄到密码管理器里链上只留密文。3. 从零跑通一条时间胶囊链环境、脚本与解锁验证3.1 环境准备与依赖安装这个项目对 Python 版本要求不高3.8 以上都能跑。我实测过 3.10 和 3.12行为一致。依赖只有cryptography其他全是标准库。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install cryptography逻辑说明虚拟环境避免污染全局包。cryptography是唯一第三方依赖用来做 Fernet 加密。如果你不想装任何东西也可以用hashlibhmac手搓加密但容易踩坑不建议。参数说明venv是标准库自带不需要额外安装。激活后pip list应该只有cryptography和它的依赖cffi、pycparser。3.2 完整上链脚本加密、挖矿、追加下面这段脚本把“加密 → 构造区块 → 挖矿 → 上链”串成一条线。你可以直接复制运行。if __name__ __main__: key generate_key() print(你的密钥请抄下来:, key.decode()) message 2025-12-31 再打开项目尾款已结清 cipher_text encrypt_message(key, message) bc Blockchain(difficulty4) prev_block bc.chain[-1] new_block Block( indexprev_block.index 1, timestamptime.time(), datacipher_text, previous_hashprev_block.hash ) proof bc.proof_of_work(new_block) bc.add_block(new_block, proof) print(区块已上链高度:, new_block.index) print(区块哈希:, new_block.hash) print(密文:, cipher_text)逻辑说明先生成密钥并打印然后加密消息。接着构造新区块previous_hash取链尾哈希。proof_of_work算出满足难度的 nonceadd_block校验后追加。运行完你会看到区块高度、哈希和密文。参数说明message可以换成任何字符串但建议不超过 500 字否则 Fernet token 会很长。difficulty4在普通笔记本上约 1 秒出块。如果你把timestamp改成未来时间链本身不会阻止但解锁逻辑可以据此判断“是否到期”。3.3 解锁验证怎么确认“时间到了才能读”解锁不是链的功能而是你本地脚本的逻辑。常见做法是遍历链找到目标区块检查timestamp是否小于当前时间然后解密。def unlock_capsule(bc, key, target_index): block bc.chain[target_index] if block.timestamp time.time(): return 未到解锁时间 try: return decrypt_message(key, block.data) except Exception as e: return f解密失败: {e} # 假设当前时间已过 print(unlock_capsule(bc, key, 1))逻辑说明unlock_capsule先比较区块时间戳和当前时间未到则拒绝。然后尝试解密失败说明密钥错误或数据被改。注意链上时间戳是上链时写入的不是未来时间——如果你想“定时”需要在构造区块时手动把timestamp设成未来某个时刻。参数说明target_index是区块高度创世块是 0第一个胶囊是 1。key必须和加密时一致。如果链被篡改decrypt_message会抛InvalidToken因为 Fernet 带 HMAC 校验。3.4 链完整性校验一眼看出有没有被改过任何时候你都可以跑一遍完整性检查确认整条链没被动过。def is_chain_valid(bc): for i in range(1, len(bc.chain)): current bc.chain[i] previous bc.chain[i-1] if current.hash ! current.compute_hash(): return False if current.previous_hash ! previous.hash: return False if not current.hash.startswith(0 * bc.difficulty): return False return True print(链是否完整:, is_chain_valid(bc))逻辑说明逐个区块检查三件事——当前哈希是否可复现、前向哈希是否匹配、是否满足难度。任何一项失败都返回False。这个函数在每次追加区块后跑一遍能立刻发现内存篡改。参数说明bc.difficulty必须和挖矿时一致否则前缀零检查会误判。如果你中途改了难度旧区块会全部失效——这也是为什么难度一旦确定就不要乱动。4. 避坑与排查时间胶囊上链时最容易翻车的五个地方4.1 现象哈希对不上链校验失败原因json.dumps没加sort_keysTrue或者字典键顺序在不同 Python 版本里不一致。 解决所有序列化统一用json.dumps(..., sort_keysTrue)并且不要用str(dict)代替。4.2 现象挖矿卡死半天不出块原因difficulty设太高比如 6 或 7普通 CPU 算不过来。 解决开发阶段降到 3演示用 4。真要高难度换hashlib.sha256为hashlib.blake2b会快一些但没必要。4.3 现象解密报InvalidToken原因密钥不对或者密文在传输中被截断、被 JSON 转义破坏。 解决密钥单独保存不要混进代码。密文存进区块前先strip()避免首尾空格。如果走网络传输用 base64 再包一层。4.4 现象时间戳是上链时间不是未来时间原因time.time()取的是当前时刻不是“解锁时刻”。 解决构造区块时手动传入未来时间戳比如time.time() 86400*30。解锁逻辑比较这个字段和当前时间。4.5 现象链越长校验越慢原因每次校验都重算所有区块哈希O(n) 复杂度。 解决缓存每个区块的哈希只在新块追加时增量校验。或者定期把旧链归档只保留最近 N 个块做快速验证。5. 进阶技巧把时间胶囊做成可验证的离线存证工具如果你想把这件事做得更“硬核”可以加一个Merkle 树把多个胶囊打包成一个区块这样一次上链能存几百条记录每条都有独立证明。做法是把每条密文哈希作为叶子节点两两配对哈希直到根节点根哈希写进区块头。验证时只需提供路径不用暴露其他内容。另一个实用技巧是链的持久化。内存链一关就没了我一般用json把整条链 dump 到文件加载时重建对象。注意Block类要加to_dict和from_dict方法否则反序列化会丢字段。def save_chain(bc, filenamechain.json): data [block.__dict__ for block in bc.chain] with open(filename, w) as f: json.dump(data, f, indent2) def load_chain(filenamechain.json): with open(filename) as f: data json.load(f) bc Blockchain(difficulty4) bc.chain [] for item in data: block Block(item[index], item[timestamp], item[data], item[previous_hash], item[nonce]) block.hash item[hash] bc.chain.append(block) return bc逻辑说明save_chain把每个区块的__dict__直接序列化简单粗暴但有效。load_chain重建Block对象后手动恢复hash因为__init__会重算哈希而重算结果和原来一致只要字段没变。如果字段变了is_chain_valid会立刻报错。参数说明filename建议带时间戳比如chain_20250101.json方便版本管理。indent2让文件可读但会增加体积生产环境可以去掉。最后说一个我自己的习惯每次上链前先把密文和密钥分别打印一次抄到纸上锁进抽屉。链上数据可以丢密钥丢了就真打不开了。从那以后我每次做时间胶囊都强制走一遍“加密 → 上链 → 校验 → 离线备份密钥”这四步少一步都不放心。希望帮到你。本文还有配套的精品资源点击获取