0

0

Go语言生成OpenSSH兼容的SSH密钥对教程

DDD

DDD

发布时间:2025-11-18 22:00:07

|

1034人浏览过

|

来源于php中文网

原创

Go语言生成OpenSSH兼容的SSH密钥对教程

本教程详细介绍了如何使用go语言生成ssh密钥对,重点在于创建符合openssh `authorized_keys`文件格式的公钥。文章将引导读者从rsa私钥的生成,到其pem编码存储,再到通过`golang.org/x/crypto/ssh`包将公钥转换为openssh标准格式并保存,提供完整的代码示例和最佳实践,确保密钥的安全性和兼容性。

Go语言生成SSH密钥对:从RSA到OpenSSH格式

在Go语言中进行安全通信或自动化部署时,经常需要程序化地生成SSH密钥对。虽然Go标准库提供了生成RSA密钥和PEM编码的功能,但要生成符合OpenSSH authorized_keys文件格式的公钥,则需要借助额外的包。本教程将详细介绍如何生成一个RSA私钥,并将其对应的公钥转换为OpenSSH可识别的格式。

1. 理解SSH密钥对和格式差异

SSH密钥对由一个私钥和一个公钥组成。私钥需要严格保密,而公钥则可以公开。当客户端尝试通过SSH连接到服务器时,服务器会使用存储在其authorized_keys文件中的公钥来验证客户端的私钥。

Go标准库中的crypto/rsa和crypto/x509包可以生成和处理RSA密钥,并将其编码为PKCS#1或PKIX格式的PEM块。然而,OpenSSH的authorized_keys文件对公钥的格式有特定的要求,它通常以ssh-rsa AAAAB3Nz...这样的字符串开头,而不是标准的PEM编码。

2. 生成RSA私钥

首先,我们需要生成一个RSA私钥。这是密钥对的基础。rsa.GenerateKey函数用于生成指定位数的RSA私钥。

立即学习go语言免费学习笔记(深入)”;

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "os"

    "golang.org/x/crypto/ssh" // 导入用于OpenSSH格式公钥的包
)

// generatePrivateKey 生成指定位数的RSA私钥
func generatePrivateKey(bits int) (*rsa.PrivateKey, error) {
    privateKey, err := rsa.GenerateKey(rand.Reader, bits)
    if err != nil {
        return nil, fmt.Errorf("生成RSA私钥失败: %w", err)
    }
    return privateKey, nil
}

注意事项:

  • rand.Reader是Go语言中加密安全的随机数源,用于生成密钥的随机部分。
  • 密钥位数(bits)通常建议选择2048或4096位,以提供足够的安全性。

3. PEM编码私钥并保存

生成私钥后,通常将其编码为PEM格式并保存到文件中。PEM是一种文本编码格式,常用于存储加密密钥和证书。

故事AI绘图神器
故事AI绘图神器

文本生成图文视频的AI工具,无需配音,无需剪辑,快速成片,角色固定。

下载
// encodeAndSavePrivateKey 将RSA私钥编码为PEM格式并保存到文件
func encodeAndSavePrivateKey(privateKey *rsa.PrivateKey, path string) error {
    // 将RSA私钥转换为PKCS#1 DER编码
    privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey)
    // 创建PEM编码块
    privateKeyBlock := &pem.Block{
        Type:  "RSA PRIVATE KEY",
        Bytes: privateKeyDER,
    }

    // 将PEM编码写入文件
    privateKeyFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) // 权限设置为0600确保私钥安全
    if err != nil {
        return fmt.Errorf("创建私钥文件失败: %w", err)
    }
    defer privateKeyFile.Close()

    if err := pem.Encode(privateKeyFile, privateKeyBlock); err != nil {
        return fmt.Errorf("PEM编码私钥失败: %w", err)
    }
    return nil
}

注意事项:

  • 私钥文件的权限应设置为0600(只有文件所有者可读写),以防止未经授权的访问。这是SSH密钥管理的最佳实践。
  • x509.MarshalPKCS1PrivateKey用于将rsa.PrivateKey转换为PKCS#1 DER编码的字节切片。
  • pem.Encode将PEM块写入输出流。

4. 生成OpenSSH格式的公钥

这是本教程的核心部分。Go标准库的crypto/rsa包生成的rsa.PublicKey无法直接用于OpenSSH的authorized_keys文件。我们需要使用golang.org/x/crypto/ssh包来完成转换。

// generateAndSavePublicKey 将RSA公钥转换为OpenSSH格式并保存到文件
func generateAndSavePublicKey(privateKey *rsa.PrivateKey, path string) error {
    // 从RSA私钥中提取公钥
    publicKey := &privateKey.PublicKey

    // 使用golang.org/x/crypto/ssh将RSA公钥转换为OpenSSH格式
    sshPublicKey, err := ssh.NewPublicKey(publicKey)
    if err != nil {
        return fmt.Errorf("转换为OpenSSH公钥格式失败: %w", err)
    }

    // 将OpenSSH公钥编码为authorized_keys文件所需的格式
    authorizedKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)

    // 将公钥写入文件
    // 注意:OpenSSH公钥文件的权限通常可以放宽,但0644是一个常见且安全的选择
    if err := ioutil.WriteFile(path, authorizedKeyBytes, 0644); err != nil {
        return fmt.Errorf("写入OpenSSH公钥文件失败: %w", err)
    }
    return nil
}

关键函数:

  • ssh.NewPublicKey(publicKey *rsa.PublicKey): 这个函数接收一个*rsa.PublicKey(或其他加密算法的公钥),并返回一个ssh.PublicKey接口类型。这是将标准Go公钥转换为golang.org/x/crypto/ssh包内部表示的关键一步。
  • ssh.MarshalAuthorizedKey(pub ssh.PublicKey): 这个函数将ssh.PublicKey接口编码为OpenSSH authorized_keys文件所需的字节切片。它会自动添加ssh-rsa前缀和密钥内容。

5. 完整示例代码

将上述函数组合起来,可以创建一个完整的程序来生成SSH密钥对并保存到指定文件。

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"

    "golang.org/x/crypto/ssh"
)

// generatePrivateKey 生成指定位数的RSA私钥
func generatePrivateKey(bits int) (*rsa.PrivateKey, error) {
    privateKey, err := rsa.GenerateKey(rand.Reader, bits)
    if err != nil {
        return nil, fmt.Errorf("生成RSA私钥失败: %w", err)
    }
    return privateKey, nil
}

// encodeAndSavePrivateKey 将RSA私钥编码为PEM格式并保存到文件
func encodeAndSavePrivateKey(privateKey *rsa.PrivateKey, path string) error {
    privateKeyDER := x509.MarshalPKCS1PrivateKey(privateKey)
    privateKeyBlock := &pem.Block{
        Type:  "RSA PRIVATE KEY",
        Bytes: privateKeyDER,
    }

    privateKeyFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
    if err != nil {
        return fmt.Errorf("创建私钥文件失败: %w", err)
    }
    defer privateKeyFile.Close()

    if err := pem.Encode(privateKeyFile, privateKeyBlock); err != nil {
        return fmt.Errorf("PEM编码私钥失败: %w", err)
    }
    return nil
}

// generateAndSavePublicKey 将RSA公钥转换为OpenSSH格式并保存到文件
func generateAndSavePublicKey(privateKey *rsa.PrivateKey, path string) error {
    publicKey := &privateKey.PublicKey

    sshPublicKey, err := ssh.NewPublicKey(publicKey)
    if err != nil {
        return fmt.Errorf("转换为OpenSSH公钥格式失败: %w", err)
    }

    authorizedKeyBytes := ssh.MarshalAuthorizedKey(sshPublicKey)

    if err := ioutil.WriteFile(path, authorizedKeyBytes, 0644); err != nil {
        return fmt.Errorf("写入OpenSSH公钥文件失败: %w", err)
    }
    return nil
}

// MakeSSHKeyPair 是一个辅助函数,用于生成SSH密钥对并保存到指定路径
func MakeSSHKeyPair(pubKeyPath, privateKeyPath string) error {
    // 1. 生成RSA私钥 (建议使用2048或4096位)
    privateKey, err := generatePrivateKey(2048)
    if err != nil {
        return err
    }

    // 2. 保存PEM编码的私钥
    if err := encodeAndSavePrivateKey(privateKey, privateKeyPath); err != nil {
        return err
    }

    // 3. 生成并保存OpenSSH格式的公钥
    if err := generateAndSavePublicKey(privateKey, pubKeyPath); err != nil {
        return err
    }

    return nil
}

func main() {
    // 定义密钥文件的路径
    homeDir, err := os.UserHomeDir()
    if err != nil {
        fmt.Printf("获取用户主目录失败: %v\n", err)
        os.Exit(1)
    }

    keyDir := filepath.Join(homeDir, ".ssh_go_keys")
    if err := os.MkdirAll(keyDir, 0700); err != nil {
        fmt.Printf("创建密钥目录失败: %v\n", err)
        os.Exit(1)
    }

    privateKeyFile := filepath.Join(keyDir, "id_rsa_go")
    publicKeyFile := filepath.Join(keyDir, "id_rsa_go.pub")

    fmt.Printf("正在生成SSH密钥对...\n私钥: %s\n公钥: %s\n", privateKeyFile, publicKeyFile)

    if err := MakeSSHKeyPair(publicKeyFile, privateKeyFile); err != nil {
        fmt.Printf("生成SSH密钥对失败: %v\n", err)
        os.Exit(1)
    }

    fmt.Println("SSH密钥对生成成功!")

    // 验证生成的文件内容 (可选)
    privateKeyContent, err := ioutil.ReadFile(privateKeyFile)
    if err != nil {
        fmt.Printf("读取私钥文件失败: %v\n", err)
    } else {
        fmt.Println("\n--- 私钥内容 (部分) ---")
        fmt.Println(string(privateKeyContent[:200]) + "...") // 打印前200字节
    }

    publicKeyContent, err := ioutil.ReadFile(publicKeyFile)
    if err != nil {
        fmt.Printf("读取公钥文件失败: %v\n", err)
    } else {
        fmt.Println("\n--- 公钥内容 ---")
        fmt.Println(string(publicKeyContent))
    }
}

运行示例:

  1. 确保你已经安装了Go环境。
  2. 在你的Go模块中运行 go get golang.org/x/crypto/ssh 来安装所需的第三方包。
  3. 将上述代码保存为 main.go。
  4. 运行 go run main.go。 程序将在你的用户主目录下创建一个.ssh_go_keys目录,并在其中生成id_rsa_go(私钥)和id_rsa_go.pub(公钥)。

总结

通过利用Go标准库的crypto/rsa和crypto/x509包来生成和PEM编码私钥,并结合golang.org/x/crypto/ssh包来生成OpenSSH兼容的公钥,我们可以轻松地在Go程序中实现SSH密钥对的自动化生成。遵循文件权限等安全最佳实践,可以确保所生成密钥的安全性。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

179

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

228

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

340

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

209

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

392

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

197

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

191

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

212

2025.06.17

Java JVM 原理与性能调优实战
Java JVM 原理与性能调优实战

本专题系统讲解 Java 虚拟机(JVM)的核心工作原理与性能调优方法,包括 JVM 内存结构、对象创建与回收流程、垃圾回收器(Serial、CMS、G1、ZGC)对比分析、常见内存泄漏与性能瓶颈排查,以及 JVM 参数调优与监控工具(jstat、jmap、jvisualvm)的实战使用。通过真实案例,帮助学习者掌握 Java 应用在生产环境中的性能分析与优化能力。

19

2026.01.20

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 4万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号