IMT星际源码 - 20181220

星际源码20181030.jpeg

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

原文链接:https://www.jianshu.com/p/dfc61136324c

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352