IPFS Private Network 探祕

How to enable

首先使用 ipfs-swarm-key-gen 生成 swarm.key

go get github.com/Kubuxu/go-ipfs-swarm-key-gen/ipfs-swarm-key-gen
ipfs-swarm-key-gen > ~/.ipfs/swarm.key

要加入一個私有網絡首先要得到這個網絡的 swarm.key 並保存到 ~/.ipfs/swarm.key 中,如果自定了 IPFS_PATH,那麼就將它放入自定義的路徑下。

當啓用私網配置後,將無法再使用默認的 bootnode 了,需要設置爲私網的 bootnode

爲了防止私網節點嘗試訪問默認的 bootnode 我們先將其清除:

ipfs bootstrap rm --all

然後像這樣來添加私網自己的 bootnode

ipfs bootstrap add <multiaddr>

例如:

ipfs bootstrap add /ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64

網絡中的 bootnode 節點與其他節點沒有任何不同,所以製作一個 bootnode 還是非常容易的。

也可以通過設置環境變量 LIBP2P_FORCE_PNET=1 來強行啓用私網配置,如果沒有正確配置 swarm.key 會導致 daemon 啓動失敗

原理分析

ipfs-swarm-key-gen

首先看看 key 是如何生成的

//github.com/Kubuxu/go-ipfs-swarm-key-gen/blob/master/ipfs-swarm-key-gen/main.go
func main() {
    key := make([]byte, 32)
    _, err := rand.Read(key)
    if err != nil {
        log.Fatalln("While trying to read random source:", err)
    }

    fmt.Println("/key/swarm/psk/1.0.0/")
    fmt.Println("/base16/")
    fmt.Print(hex.EncodeToString(key))
}

是不是太簡單了?生成一個 32 位的隨機數,用 hex.Encode 成一個 64 位 16進制數,談下一話題。

How to use swarm.key

首先我們來看一下 libp2p 中的 pnet 接口和實現
  • go-libp2p-interface-pnet
// go-libp2p-interface-pnet/interface.go
// Protector interface is a way for private network implementation to be transparent in
// libp2p. It is created by implementation and use by libp2p-conn to secure connections
// so they can be only established with selected number of peers.
type Protector interface {
    // Wraps passed connection to protect it
    Protect(net.Conn) (net.Conn, error)

    // Returns key fingerprint that is safe to expose
    Fingerprint() []byte
}

Protector 接口說它是實現私網的一種方式,由 libp2p-conn 調用創建加密連接

  • go-libp2p-pnet
// go-libp2p-pnet/protector.go
type protector struct {
    psk         *[32]byte
    fingerprint []byte
}

func (p protector) Protect(in net.Conn) (net.Conn, error) {
    return newPSKConn(p.psk, in)
}
func (p protector) Fingerprint() []byte {
    return p.fingerprint
}

這個 protector 類實現了 Protector 接口,在創建這個類時會去反序列化 key 得到 psk ,那個就不重要了,比較重要的是 newPSKConn 這個包裝

func newPSKConn(psk *[32]byte, insecure net.Conn) (net.Conn, error) {
    if insecure == nil {
        return nil, errInsecureNil
    }
    if psk == nil {
        return nil, errPSKNil
    }
    return &pskConn{
        Conn: insecure,
        psk:  psk,
    }, nil
}

上面的 newPSKConn 返回了 pskConn ,這個類繼承了 net.Conn並且對 readwrite 進行了有關加密解密的封裝

type pskConn struct {
    net.Conn
    psk *[32]byte

    writeS20 cipher.Stream
    readS20  cipher.Stream
}

func (c *pskConn) Read(out []byte) (int, error) {
    if c.readS20 == nil {
        // 如果是第一個包,那麼前 24 個字節一定是一個隨機數
        // 因爲下面的 Write 方法第一個包就是放了一個 24 byte 的 nonce 在前面
        nonce := make([]byte, 24)
        _, err := io.ReadFull(c.Conn, nonce)
        if err != nil {
            return 0, errShortNonce
        }
        // salsa20.New 這個實現過程比較複雜,目的是返回一個標準庫中 cipher.Stream 接口的實現類
        c.readS20 = salsa20.New(c.psk, nonce)
    }

    maxn := uint32(len(out))
    in := mpool.ByteSlicePool.Get(maxn).([]byte) // get buffer
    defer mpool.ByteSlicePool.Put(maxn, in)      // put the buffer back

    in = in[:maxn]            // truncate to required length
    n, err := c.Conn.Read(in) // read to in
    if n > 0 {
        // 代碼看到這裏就可以省略後面的處理了,因爲答案已經出來了
        // 直接去看標準庫中對於 cipher.Stream.XORKeyStream 的定義即可
        c.readS20.XORKeyStream(out[:n], in[:n]) // decrypt to out buffer
    }
    return n, err
}

func (c *pskConn) Write(in []byte) (int, error) {
    ......
    c.writeS20.XORKeyStream(out, in) // encrypt
    return c.Conn.Write(out) // send
}

ReadWrite 的實現中看到了 XORKeyStream ,字面能猜出這是通過數據包和 key 進行異或操作而實現的簡單的加密傳輸,兩邊的key肯定是一致的,並且每次建立連接時都會交換一個 24 位的 nonce,細心的同學可以去看一下具體的拆分邏輯,分成了 前16 和後 8 ,又做了一些複雜的位移操作用來生成連接通道上使用的key,隨機數保證了每次連接通道上的 key 都是不同的。

誰在使用 Protector ?
  • 答案:go-libp2p-transport-upgrader

libp2p 中定義了很多 Option 用來初始化 Host 對象,在前面的幾篇入門文章中提及過,其中就有一個叫 libp2p.PrivateNetworkoption,用來爲 Host 指定 Protector 。前面的文章也提到過 TcpTransportUpgrader 的調用過程,這個 Protector 就是在 Upgrader.upgrade 中被使用到的,有興趣的同學可以重新回憶並翻找一下代碼。

總結

現在我們知道了私網是通過對通道進行加密來建立的,發出的數據包都用密鑰進行一次異或 XOR(packet,securityKey) ,接收端再做一次相同的操作即可還原數據包。這個原理計算機專業的同學應該都瞭解,如果不瞭解就演算一下

  • 演算過程: 設 packet = 1 , securityKey = 1
    securityPacket = xor(packet, securityKey) = xor(1,1) = 0
    packet = xor(securityPacket,securityKey) = xor(0,1) = 1
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章