doulan1866 2018-08-19 16:42
浏览 93
已采纳

加密聊天中的“运行时错误:切片范围超出范围”

Update: thanks to peterSO the error seems to be that random bytes, read as strings will include " " which causes a newline and the error. The problem is neither

io.Copy(conn, bytes.NewReader(encrypted))

nor

conn.Write([]byte(encrypted))

work. Anyone has an idea how to write the chipertext to conn?

Original post: The chat program consists of one server and two clients. It uses TLS and NaCl for (end-to-end-)encryption. In 3/4 of cases it works, but sometimes I get an error:

panic: runtime error: slice bounds out of range

goroutine 34 [running]:
main.handleConnection(0x600a60, 0xc04246c000)
    path-to/client.go:79 
+0x3a6
created by main.main
    path-to/client.go:44 
+0x436
exit status 2

Line 44 calls

go handleConnection(conn)

Line 79 is the "decrypted" line:

func handleConnection(conn net.Conn) {
    defer conn.Close()
    input := bufio.NewScanner(conn)
    for input.Scan() {
        senderPublicKey := readKey("localPublic")
        recipientPrivateKey := readKey("remotePrivate")
        var decryptNonce [24]byte
        encrypted := input.Bytes()
        copy(decryptNonce[:], encrypted[:24])
        decrypted, ok := box.Open(nil, encrypted[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
        if !ok {
            fmt.Println("decryption error")
        }
        fmt.Println(BytesToString(decrypted))
    }
}

The full code is further down. As it works flawlessly without encryption and a test-implementation of just the encryption also works, I would point to the transmission between client-server-client. Normally the length of the slice shouldn't change, as the output should remain the same?

The client reads:

package main

import (
    "bufio"
    crypto_rand "crypto/rand"
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net"
    "os"

    "golang.org/x/crypto/nacl/box"
)

func main() {
    cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
    if err != nil {
        log.Fatalln("Unable to load cert", err)
    }

    clientCACert, err := ioutil.ReadFile("cert.pem")
    if err != nil {
        log.Fatal("Unable to open cert", err)
    }

    clientCertPool := x509.NewCertPool()
    clientCertPool.AppendCertsFromPEM(clientCACert)

    conf := &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      clientCertPool,
        //InsecureSkipVerify: true,
    }

    conn, err := tls.Dial("tcp", "localhost:443", conf)
    if err != nil {
        log.Fatal(err)
    }

    defer conn.Close()
    go handleConnection(conn)
    for {
        stdin := bufio.NewReader(os.Stdin)
        textIn, err := stdin.ReadBytes('
')
        if err != nil {
            fmt.Println(err)
        }
        var nonce [24]byte
        if _, err := io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil {
            panic(err)
        }
        senderPrivateKey := readKey("localPrivate")
        recipientPublicKey := readKey("remotePublic")
        encrypted := box.Seal(nonce[:], textIn, &nonce, recipientPublicKey, senderPrivateKey)
        text := BytesToString(encrypted)
        fmt.Fprintf(conn, text+"
")
    }

}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    input := bufio.NewScanner(conn)
    for input.Scan() {
        senderPublicKey := readKey("localPublic")
        recipientPrivateKey := readKey("remotePrivate")
        var decryptNonce [24]byte
        encrypted := input.Bytes()
        copy(decryptNonce[:], encrypted[:24])
        decrypted, ok := box.Open(nil, encrypted[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
        if !ok {
            fmt.Println("decryption error")
        }
        fmt.Println(BytesToString(decrypted))
    }
}

//BytesToString converts []byte to str
func BytesToString(data []byte) string {
    return string(data[:])
}

//Read the keys from file, pass filename without .ending
func readKey(name string) (prv *[32]byte) {
    prv = new([32]byte)
    f, err := os.Open(name + ".key")
    if err != nil {
        panic(err)
    }
    _, err = io.ReadFull(f, prv[:])
    if err != nil {
        panic(err)
    }

    return
}

The server side:

package main

import (
    "bufio"
    "crypto/tls"
    "fmt"
    "log"
    "net"
)


type client chan<- string // an outgoing message channel

var (
    entering = make(chan client)
    leaving  = make(chan client)
    messages = make(chan string) // all incoming client messages
)

// Broadcast incoming message to all clients' outgoing message channels.
func broadcaster() {
    clients := make(map[client]bool) // all connected clients
    for {
        select {
        case msg := <-messages:
            for cli := range clients {
                cli <- msg
            }
        case cli := <-entering:
            clients[cli] = true
        case cli := <-leaving:
            delete(clients, cli)
            close(cli)
        }
    }
}

func handleConn(conn net.Conn) {
    ch := make(chan string) // outgoing client messages
    go clientWriter(conn, ch)

    //who := conn.RemoteAddr().String()
    entering <- ch
    //messages <- who + " has arrived"
    input := bufio.NewScanner(conn)
    for input.Scan() {
        messages <- input.Text()
    }
    //messages <- who + " has left"
    leaving <- ch
    conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
    for msg := range ch {
        fmt.Fprintln(conn, msg)
    }
}




func main() {
    cer, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
    if err != nil {
        log.Println(err)
        return
    }

    config := &tls.Config{
        Certificates: []tls.Certificate{cer},
        //PFS, this will reject client with RSA certificates
        CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
        //Force it server side
        PreferServerCipherSuites: true,
        //Force TLS Version
        MinVersion: tls.VersionTLS12}

    listener, err := tls.Listen("tcp", "localhost:443", config)
    if err != nil {
        log.Fatal(err)
    }

    go broadcaster()
    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Print(err)
            continue
        }
        go handleConn(conn)
    }
}
  • 写回答

1条回答 默认 最新

  • douliandan7340 2018-08-19 20:09
    关注

    For no apparent reason, you hope that len(input.Bytes()) >= 24. When it is not: panic: runtime error: slice bounds out of range.

    For example,

    package main
    
    func main() {
        /*
           var decryptNonce [24]byte
           encrypted := input.Bytes()
           copy(decryptNonce[:], encrypted[:24])
           decrypted, ok := box.Open(nil, encrypted[24:], &decryptNonce, senderPublicKey, recipientPrivateKey)
           if !ok {
               fmt.Println("decryption error")
           }
        */
    
        var decryptNonce [24]byte
        encrypted := make([]byte, 23, 24) // len(input.Bytes()) < 24
        copy(decryptNonce[:], encrypted[:24])
        // panic: runtime error: slice bounds out of range
        _ = encrypted[24:]
    }
    

    Playground: https://play.golang.org/p/LZ34NgLV84G

    Output:

    panic: runtime error: slice bounds out of range
    
    goroutine 1 [running]:
    main.main()
        /tmp/sandbox792172306/main.go:18 +0xa0
    

    Comment by Halux9000:

    This is highly probably the cause. But len(input.Bytes()) >= 24 should be true when input.Bytes() is generated via

    var nonce [24]byte
    if _, err := io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil {
      panic(err)
    }
    senderPrivateKey := readKey("localPrivate")
    recipientPublicKey := readKey("remotePublic")
    encrypted := box.Seal(nonce[:], textIn, &nonce, recipientPublicKey, senderPrivateKey)
    text := BytesToString(encrypted)
    fmt.Fprintf(conn, text+"
    ")
    

    Encryption without transmission works. So where is it shortened / changed?

    I'm not persuaded by your "should" argument. I believe the program.

    If you have random or encrypted bytes then some of them are going to be newlines. I calculated the expected percentage of lines with a newline in the first 24 bytes (the nonce) as 8.966% and confirmed that by experiment.

    package main
    
    import (
        "bytes"
        "crypto/rand"
        "fmt"
        "io"
    )
    
    var nonce [24]byte
    
    func expected() float64 {
        e := 0.0
        for range nonce {
            e += (float64(len(nonce)) - e) / 256
        }
        return e * 100 / float64(len(nonce))
    }
    
    func actual() float64 {
        a, n := 0, 1024*1024
        for i := 0; i < n; i++ {
            if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
                panic(err)
            }
            if bytes.IndexByte(nonce[:], '
    ') >= 0 {
                a++
            }
        }
        return float64(a*100) / float64(n)
    }
    
    func main() {
        fmt.Printf("expected: %.3f%%
    ", expected())
        fmt.Printf("actual:   %.3f%%
    ", actual())
    }
    

    Playground: https://play.golang.org/p/cKJlGUSbi3u

    Output:

    $ go run newlines.go
    expected: 8.966%
    actual:   8.943%
    $ go run newlines.go
    expected: 8.966%
    actual:   8.956%
    $ go run newlines.go
    expected: 8.966%
    actual:   8.976%
    $ go run newlines.go
    expected: 8.966%
    actual:   8.992%
    $
    

    Comment by Halux9000:

    Would you advise another method of sending ciphertext bytes to conn?

    You need a more robust message protocol that is not sensitive to message content. For example, prefix the message content with the message content length.

    A simple illustration,

    package main
    
    import (
        "bytes"
        "encoding/binary"
        "fmt"
        "io"
    )
    
    func main() {
        // Connection
        var conn = new(bytes.Buffer)
    
        {
            // Server
            buf := make([]byte, 0, 2+64*1024)
            msgLen := uint16(16)
            buf = buf[0 : 2+msgLen]
            binary.BigEndian.PutUint16(buf[0:2], msgLen)
            for i := uint16(0); i < msgLen; i++ {
                buf[2+i] = byte(i)
            }
            fmt.Println(msgLen, len(buf), buf)
            n, err := conn.Write(buf)
            if err != nil {
                fmt.Println(n, err)
                return
            }
        }
    
        {
            // Client
            buf := make([]byte, 0, 2+64*1024)
            n, err := io.ReadFull(conn, buf[:2])
            if err != nil {
                fmt.Println(n, err)
                return
            }
            msgLen := binary.BigEndian.Uint16(buf[0:2])
            buf = buf[0 : 2+msgLen]
            n, err = io.ReadFull(conn, buf[2:2+msgLen])
            if err != nil {
                fmt.Println(err)
                return
            }
            fmt.Println(msgLen, len(buf), buf)
        }
    }
    

    Playground: https://play.golang.org/p/nS3xpFrG4uB

    Output:

    16 18 [0 16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
    16 18 [0 16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 怎么让wx群机器人发送音乐
  • ¥15 fesafe材料库问题
  • ¥35 beats蓝牙耳机怎么查看日志
  • ¥15 Fluent齿轮搅油
  • ¥15 八爪鱼爬数据为什么自己停了
  • ¥15 交替优化波束形成和ris反射角使保密速率最大化
  • ¥15 树莓派与pix飞控通信
  • ¥15 自动转发微信群信息到另外一个微信群
  • ¥15 outlook无法配置成功
  • ¥30 这是哪个作者做的宝宝起名网站