You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

发送至Pyodide时数据验证失败,SQLite存储签名后验证异常

ECDSA签名存入SQLite后验证失败问题排查与解决方案

你的代码本地运行时签名验证正常,但将签名经过Base64编码存入SQLite再取回解码后验证失败,核心问题大概率出在签名的编码/解码环节或者数据库存储的字符串处理上,以下是具体的排查和解决方法,同时适配Flask+Pyodide的认证场景。

核心问题分析

  1. Base64编码/解码不匹配:如果存储时用了普通Base64编码,取回时误用了URL安全的Base64解码,或者编码后没有正确转为UTF-8字符串存储,会导致解码后的签名二进制数据和原签名不一致。
  2. SQLite字符串存储的隐性修改:SQLite存储字符串时如果没有正确处理特殊字符(比如Base64中的+、/),可能会被自动转义或截断,导致取回的Base64字符串失真。
  3. 手动哈希的一致性问题:你当前手动计算消息摘要的方式,若在存储消息或取回消息时编码不一致(比如字符串转bytes用了不同编码),会导致摘要不匹配,进而验证失败。

修正后的实现方案

1. 规范签名的编码/解码流程

签名是二进制bytes类型,不能直接存入SQLite,必须先转为Base64字符串;取回时再解码回bytes。使用标准的Base64编码(避免URL安全变体,除非你明确需要):

import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key
import sqlite3
import os

# 用os.path.join处理路径,适配跨平台
private_key_path = os.path.join("CA_test", "private_key.pem")
public_key_path = os.path.join("CA_test", "public_key.pem")

with open(private_key_path, 'rb') as f:
    private_key = load_pem_private_key(f.read(), password=None)    

with open(public_key_path, 'rb') as f:
    public_key = load_pem_public_key(f.read())    

# 待签名的消息(统一用utf-8转bytes)
message = b'14789'

# 生成签名(推荐直接传入消息,让库自动计算哈希,避免手动哈希出错)
signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))

# 将签名转为Base64字符串,用于存入SQLite
signature_b64 = base64.b64encode(signature).decode('utf-8')

# 存入SQLite示例
conn = sqlite3.connect('auth.db')
cursor = conn.cursor()
# 先创建表(首次运行)
cursor.execute("CREATE TABLE IF NOT EXISTS user_auth (message TEXT PRIMARY KEY, signature TEXT)")
cursor.execute("INSERT OR REPLACE INTO user_auth (message, signature) VALUES (?, ?)", (message.decode('utf-8'), signature_b64))
conn.commit()
conn.close()

# 从SQLite取回并验证示例
conn = sqlite3.connect('auth.db')
cursor = conn.cursor()
cursor.execute("SELECT signature FROM user_auth WHERE message = ?", (message.decode('utf-8'),))
row = cursor.fetchone()
stored_signature_b64 = row[0]
conn.close()

# 解码Base64字符串回签名bytes
stored_signature = base64.b64decode(stored_signature_b64)

# 验证签名(同样直接传入消息,库自动计算哈希)
try:
    public_key.verify(stored_signature, message, ec.ECDSA(hashes.SHA256()))
    print('验证通过,用户认证成功')
except Exception as e:
    print(f'验证失败,认证失败: {str(e)}')

2. Flask后端适配要点

在Flask接口中,要确保传递的签名Base64字符串不被篡改,比如:

  • 接收前端(Pyodide)传来的消息和签名时,用request.get_json()获取原始JSON数据,避免表单编码导致的字符转义
  • 返回签名给前端时,直接返回Base64字符串,不要额外添加换行或空格

示例Flask接口:

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/auth/sign', methods=['POST'])
def generate_signature():
    data = request.get_json()
    message = data['message'].encode('utf-8')
    # 加载密钥(建议缓存密钥,不要每次请求都读取文件)
    with open(private_key_path, 'rb') as f:
        private_key = load_pem_private_key(f.read(), password=None)
    signature = private_key.sign(message, ec.ECDSA(hashes.SHA256()))
    signature_b64 = base64.b64encode(signature).decode('utf-8')
    # 存入数据库
    conn = sqlite3.connect('auth.db')
    cursor = conn.cursor()
    cursor.execute("INSERT OR REPLACE INTO user_auth (message, signature) VALUES (?, ?)", (data['message'], signature_b64))
    conn.commit()
    conn.close()
    return jsonify({'signature': signature_b64})

@app.route('/auth/verify', methods=['POST'])
def verify_signature():
    data = request.get_json()
    message = data['message'].encode('utf-8')
    stored_signature_b64 = data['signature']
    try:
        stored_signature = base64.b64decode(stored_signature_b64)
        with open(public_key_path, 'rb') as f:
            public_key = load_pem_public_key(f.read())
        public_key.verify(stored_signature, message, ec.ECDSA(hashes.SHA256()))
        return jsonify({'status': 'success', 'message': '认证通过'})
    except Exception as e:
        return jsonify({'status': 'fail', 'message': f'认证失败: {str(e)}'}), 401

if __name__ == '__main__':
    app.run(debug=True)

3. Pyodide前端适配要点

在Pyodide中,确保消息编码和签名的Base64处理和后端一致:

  • 将用户输入的消息转为UTF-8编码的字符串
  • 后端返回的签名直接用于验证,不要额外处理

示例Pyodide代码片段:

async function verifyAuth() {
    const message = "14789";
    // 先请求签名(或从本地存储获取)
    const signResponse = await fetch('/auth/sign', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({message: message})
    });
    const signData = await signResponse.json();
    const signature = signData.signature;

    // 请求验证
    const verifyResponse = await fetch('/auth/verify', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({message: message, signature: signature})
    });
    const verifyData = await verifyResponse.json();
    console.log(verifyData.message);
}

关键注意事项

  • 避免手动计算哈希:cryptography的sign和verify方法可以直接处理原始消息,自动计算正确的SHA-256哈希,手动计算容易因为编码、字节顺序等问题出错。
  • 统一字符编码:所有字符串转bytes的操作都用utf-8编码,避免因编码不一致导致的消息哈希差异。
  • 检查SQLite存储的字符串:可以直接查询数据库查看存储的Base64字符串是否和生成的完全一致,排除数据库自动修改的可能。

内容的提问来源于stack exchange,提问作者Mr.Deadpool

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:35:02