SSH 密钥认证深度解析 | AI生成和翻译

Home 2026.03

SSH 密钥认证 — 深入解析

什么是 SSH 密钥对?

当你运行 ssh-keygen 时,它会创建两个数学上相关的文件:

id_ed25519       → Private Key (SECRET, never leave your machine)
id_ed25519.pub   → Public Key (share freely, paste into servers)

对于 Ed25519(现代标准),它们实际包含的内容如下:

Private key (id_ed25519):

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
c2gtZWQyNTUxOQAAACB4G3kR2f8J...  (base64 encoded binary data)
-----END OPENSSH PRIVATE KEY-----

包含:你的 private scalar(一个 256 位秘密数字)+ 你的 public key + 元数据。

Public key (id_ed25519.pub):

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMTRoCy6Uo67vc1CJSAAnZQCHftNB5kbq9jf8ZsEsa+o lzwjava@gmail.com

三个部分:

Part Value Meaning
ssh-ed25519 Key type Algorithm used
AAAAC3NzaC... Public key 256-bit public point (base64)
lzwjava@gmail.com Comment Your label (optional)

Ed25519 背后的数学原理

基于 Curve25519 椭圆曲线:

Private key = random 256-bit number (k)
Public key  = k × G  (point multiplication on the curve)

G = base point (fixed, everyone agrees on it)

Given k → easy to compute k×G     (one operation)
Given k×G → impossible to find k  (would need to solve ECDLP)

这种 one-way trapdoor 是你为什么可以自由共享公钥的原因。

认证握手(逐步解析)

当你的 Mac 连接到 Gitea 时:

Client (Mac)                          Server (Gitea Container)
    |                                        |
    |--- TCP connection to :2222 ----------->|
    |<-- "Hello, I'm an SSH server" ---------|
    |--- "Hello, I'm an SSH client" -------->|
    |                                        |
    |         [ Key Exchange - Diffie-Hellman ]|
    |                                        |
    |--- eph_pub_key_A ------------------>   |
    |<-- eph_pub_key_B -------------------   |
    |                                        |
    |   Both compute: shared_secret = A×B    |
    |   Session encryption key derived       |
    |                                        |
    |         [ User Authentication ]         |
    |                                        |
    |--- "I am 'git', prove my key" ------->|
    |                                        |
    |    Server checks ~/.ssh/authorized_keys|
    |    Finds matching public key           |
    |                                        |
    |<-- challenge = random data + signature |
    |    signed with server's HOST key       |
    |                                        |
    |--- signature(challenge, priv_key) --->|
    |                                        |
    |    Server verifies signature using     |
    |    the public key from authorized_keys |
    |                                        |
    |    sig == expected? → ✅ AUTHENTICATED |
    |                                        |
    |--- "shell access granted" ----------->|

签名步骤(你的修复起作用的地方)

这是关键时刻:

1. Server generates random bytes: R = [0x4a, 0x1f, 0x8c, ...]

2. Server signs R with its HOST private key (proves it's the real server)
   Server sends: (R, server_signature)

3. Client verifies server's signature (anti-MITM check)

4. Client signs R with its USER private key
   Client sends: client_signature = Sign(R, private_key)

5. Server verifies:
   Verify(client_signature, R, public_key_from_authorized_keys) → true/false

步骤 5 是你之前失败、之后成功的地方:

BEFORE (Mac had different key):
  Client signs with: key_B_private
  Server checks against: key_A_public (only key in authorized_keys)
  Verify(key_B_sig, R, key_A_pub) → ❌ MATH DOESN'T CHECK OUT

AFTER (Mac had same key as server):
  Client signs with: key_A_private
  Server checks against: key_A_public (same key in authorized_keys)
  Verify(key_A_sig, R, key_A_pub) → ✅ PERFECT MATCH

Gitea 中的 authorized_keys 样子

# Inside the Gitea container at /data/git/.ssh/authorized_keys:

# gitea public key
command="/usr/local/bin/gitea --config=/data/gitea/conf/app.ini serv key-1",
no-port-forwarding,
no-X11-forwarding,
no-agent-forwarding,
no-pty,
no-user-rc,
restrict
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMTRoCy6Uo67vc1CJSAAnZQCHftNB5kbq9jf8ZsEsa+o user-1

关键细节:

Part Purpose
command="...gitea serv key-1" Forces the connection into Gitea’s handler (not a shell)
no-port-forwarding etc. Security restrictions — you can only use Git, not tunnel
restrict Master switch for all the no-* options
ssh-ed25519 AAAA... The actual public key to match against
key-1 Gitea’s internal key ID

为什么 SSH 密钥优于密码

Password auth:
  Client sends password over encrypted channel
  Server hashes it, compares to stored hash
  Problem: password can be brute-forced, phished, reused

Key auth:
  Client NEVER sends the private key
  Client proves it HAS the key by solving a mathematical challenge
  Even if someone captures the entire session, they learn nothing about the private key
  The private key never leaves your machine (unless you copy it, like you did 😉)

你的具体设置

┌─────────────────────────────────────────────────────┐
│  Gitea Container (192.168.1.36)                     │
│  ┌─────────────────────────────────────────────┐    │
│  │  /data/git/.ssh/authorized_keys             │    │
│  │  ┌─────────────────────────────────────┐    │    │
│  │  │ ssh-ed25519 AAAA...sa+o user-1      │    │    │
│  │  │  ↑                                  │    │    │
│  │  │  Public key trusted by Gitea        │    │    │
│  │  └─────────────────────────────────────┘    │    │
│  └─────────────────────────────────────────────┘    │
│         ▲                      ▲                     │
│         │ matches              │ matches             │
│  ┌──────┴──────┐       ┌──────┴──────┐              │
│  │ Server      │       │ Mac         │              │
│  │ id_ed25519  │       │ id_ed25519  │ (copied)     │
│  │ (private)   │       │ (private)   │              │
│  │ AAAA...sa+o │       │ AAAA...sa+o │              │
│  └─────────────┘       └─────────────┘              │
└─────────────────────────────────────────────────────┘

Both machines have the SAME private key.
Gitea trusts that key.
So both machines can authenticate. ✅

你的修复核心:你让两台机器共享 Gitea 已信任的一个身份。 🦞


Back Donate