見出し画像

Goで簡単に実装!パスワード付きZIPでデータ保護を強化

この記事では、Goでパスワード保護されたZIPファイルを作成する方法をご紹介します。具体的には、ユーザーがファイルをアップロードし、サーバー側でそれを ZIP ファイルに圧縮する実装をみていきます。

  1. ファイルアップロード:ユーザーがファイルをアップロード

  2. Goを使用したファイル圧縮:Goライブラリを使用してファイルをZIP形式に圧縮

  3. パスワード保護の追加:機密データを保護するため、ZIPファイルにパスワードを設定

前提条件

Goは標準ライブラリを通じてZIPファイルの作成をネイティブにサポートしています。しかし、ZIPアーカイブにパスワード保護を追加してセキュリティを強化したい場合は、サードパーティのパッケージを活用する必要があります。ここでは、パスワード保護されたZIPファイルの操作機能を提供するgithub.com/alexmullins/zip パッケージを使用します。

go get -u github.com/alexmullins/zip

また、API を実装するために、Gin Web フレームワークを使用します。

go get -u github.com/gin-gonic/gin

コードの実装

まずは、ファイルのアップロードと標準的なZIP圧縮プロセスを処理する基本的なGinアプリケーションを作成しましょう。

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    // Initialize Gin router
    r := gin.Default()

    // Route for uploading files
    r.POST("/upload",func(c *gin.Context){
        
        // Retrieve the uploaded file
        file, err := c.FormFile("file")
        // Handle errors...

        // Open the uploaded file
        uploadedFile, err := file.Open()
        // Handle errors...

        defer uploadedFile.Close()
    
        // Create a buffer to write the zip file to
        buf := new(bytes.Buffer)
    
        // Zip the uploaded file
        err = zipFile(buf, file.Filename, uploadedFile)
        
        //send to user or upload to storage service

    })

    
    r.Run(":8080")
}

func zipFiles(buf *bytes.Buffer, filename string, file io.Reader) error {
    //handle zipping process
}

上記は、ユーザーがファイルをアップロードするための基本的なルートを持つ Gin のコードです。ファイルの取得し、開き、ZIP圧縮プロセス後のクローズなど、一連の処理を行います。

ここでは、処理されたZIPファイルを保存するためにバッファを使用し、直接ユーザーに送信していますが、必要に応じてストレージサービスにアップロードすることもできます。

では、ZIP ファイルを作成する処理をみていきましょう。これを実現するため、Go の archive/zip パッケージを使用します。

func zipFiles(buf *bytes.Buffer, filename string, file io.Reader) error {
    zipWriter := zip.NewWriter(buf)
    fileInfo, err := zipWriter.Create(filename)
    // Handle errors...
    
    _, err = io.Copy(fileInfo, file)
    // Handle errors...
    
    err = zipWriter.Close()
    // Handle errors...
    
    return nil
}

コードを詳しく見ていきましょう。

  • まず、zip.NewWriter(buf) を使用して新しい zipWriter を作成します。これにより、提供されたバッファにZIPファイルを書き込む新しいZIPアーカイブライターが初期化されます。

  • 次に、zipWriter.Create(filename)を使用してZIPアーカイブ内に新しいファイルを作成します。このメソッドは、ファイルの内容を書き込むことができるio.WriterとしてfileInfoを返します。filenameはZIPアーカイブ内のファイル名です。fileInfoに書き込まれた内容は、指定されたファイル名でZIPアーカイブに追加されます。

  • その後、io.Copyを使用して、元のファイル(file)の内容をZIPアーカイブ内のファイル(fileInfo)にコピーします。これにより、元のファイルの内容が効果的にZIPアーカイブに追加されます。

  • ファイルをZIPアーカイブに追加した後、zipWriter.Close()を使用してzipWriterを閉じます。これにより、必要なすべてのデータがフラッシュされ、バッファに書き込まれることが保証されます。クローズ中に発生したエラーはerr変数に割り当てられます。

コンテンツのメタ情報(タイムスタンプ、圧縮方法、圧縮サイズ、またはコンテンツに関する追加情報など)を変更したい場合、ファイル名のみを受け付ける zip writer の Create メソッドを使用する代わりに、修正されたヘッダーを持つ CreateHeader メソッドを使用できることです。

header := &zip.FileHeader{
    Name:   filename, // Name of the file within the zip archive
    method: zip.Deflate 
    // Other attributes...
    
}

 zipWriter := zip.NewWriter(buf)
    
 // Create a file within the zip archive with custom header
 fileInfo, err := zipWriter.CreateHeader(header)
 // Handle errors...

// Continue

すべてのプロセスの後、実際のコードは次のようになります。

package main

import (
    "archive/zip"
    "bytes"
    "github.com/gin-gonic/gin"
    "io"
    "net/http"
)

func main() {
    // Initialize Gin router
    router := gin.Default()

    // Define a route to handle file uploads
    router.POST("/upload", handleFileUpload)

    // Start the server
    router.Run(":8080")
}

func handleFileUpload(c *gin.Context) {
    // Retrieve the uploaded file
    file, err := c.FormFile("file")
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Open the uploaded file
    uploadedFile, err := file.Open()
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to open file"})
        return
    }
    defer uploadedFile.Close()

    // Create a buffer to write the zip file to
    buf := new(bytes.Buffer)

    // Zip the uploaded file
    err = zipFiles(buf, file.Filename, uploadedFile)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to zip file"})
        return
    }

    // Set the response headers
    c.Header("Content-Description", "File Transfer")
    c.Header("Content-Disposition", "attachment; filename="+file.Filename+".zip")
    c.Header("Content-Type", "application/zip")
    c.Header("Content-Length", string(buf.Len()))

    // Send the zip file back to the user
    c.Data(http.StatusOK, "application/zip", buf.Bytes())
}

func zipFiles(buf *bytes.Buffer, filename string, file io.Reader) error {
    // Create a new zip archive
    zipWriter := zip.NewWriter(buf)

    // Add the file to the zip archive
    fileInfo, err := zipWriter.Create(filename)
    if err != nil {
        return err
    }

    // Copy the file data to the zip archive
    _, err = io.Copy(fileInfo, file)
    if err != nil {
        return err
    }

    // Close the zip writer
    err = zipWriter.Close()
    if err != nil {
        return err
    }

    return nil
}

ZIPにパスワードを追加する

Go標準アーカイブはアーカイブファイルにパスワードを追加する機能を提供していないため、サードパーティライブラリの alexmullins/zip を使用します。このパッケージは標準パッケージの上に書かれているため、ZIP圧縮プロセスは標準ライブラリと同じですが、パスワードを追加する追加機能があります。

まず、上記のコードで、"archive/zip" を "github.com/alexmullins/zip" に変更します。ZIPファイルにパスワードを追加するために、通常の標準ライブラリでは存在しない zipWriter の Encrypt メソッドを使用します。

func zipFiles(buf *bytes.Buffer, filename string, file io.Reader) error {
    zipWriter := zip.NewWriter(buf)
    fileInfo, err := zipWriter.Encrypt(filename,"secret")
    // Handle errors...
    
    _, err = io.Copy(fileInfo, file)
    // Handle errors...
    
    err = zipWriter.Close()
    // Handle errors...
    
    return nil
}

以上です。少しでも参考になれば幸いです。


この記事は、2024年10月に弊社のエンジニア Rajan Sapkota が執筆した内容を日本語に翻訳したものです。
英語版はこちらをご覧ください。
https://articles.wesionary.team/file-zipping-with-password-protection-using-go-e3115f3baf5c


採用情報

私たちはプロダクト共創の仕組み化に取り組んでいます。プロダクト共創をリードするプロダクト・マネージャー、そして、私たちのビジョンを市場に届ける営業メンバーを募集しています!


開発パートナーをお探しの企業様へ

弊社は、グローバル開発のメリットを活かし、高い費用対効果と品質を両立しています。経験豊富で多様性のあるチームが、課題を正しく理解し、最適なシステムと優れた体験を実現します。業務システムの開発、新規事業の開発、業務効率化やDX化に関するお困りごと、ぜひ弊社にご相談ください。