{"id":13489935,"url":"https://github.com/mjpitz/gorm-encryption","last_synced_at":"2025-07-13T19:36:04.762Z","repository":{"id":176688290,"uuid":"652095819","full_name":"mjpitz/gorm-encryption","owner":"mjpitz","description":"Easily encrypt data at rest in relational databases using drop-in Gorm Serializers.","archived":false,"fork":false,"pushed_at":"2023-12-05T19:27:54.000Z","size":46,"stargazers_count":8,"open_issues_count":3,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-28T08:33:55.790Z","etag":null,"topics":["go","golang","gorm","gorm-serializer"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mjpitz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-06-11T04:24:13.000Z","updated_at":"2025-01-28T22:15:46.000Z","dependencies_parsed_at":"2024-01-16T09:01:16.809Z","dependency_job_id":"67070291-8f6f-4824-b3c5-22774d417b06","html_url":"https://github.com/mjpitz/gorm-encryption","commit_stats":null,"previous_names":["mjpitz/gorm-encryption"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjpitz%2Fgorm-encryption","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjpitz%2Fgorm-encryption/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjpitz%2Fgorm-encryption/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mjpitz%2Fgorm-encryption/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mjpitz","download_url":"https://codeload.github.com/mjpitz/gorm-encryption/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243911165,"owners_count":20367638,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","gorm","gorm-serializer"],"created_at":"2024-07-31T19:00:38.128Z","updated_at":"2025-03-18T09:31:00.693Z","avatar_url":"https://github.com/mjpitz.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# gorm-encryption\n\nEasily encrypt data at rest in relational databases using drop-in Gorm Serializers.\n\n**Ok, but _how?_**\n\nThis library draws inspiration from how SOPS handles encrypting fields in a simple configuration file as well as how\nBadgerDB implements its encryption key management behind the scenes. When a `[]byte` field is encrypted using this\nlibrary, it's formatted as follows: `ENC:algorithm,fingerprint,ciphertext`. The `algorithm` is a single byte\nrepresenting `aes` (1) or `aes-gcm` (2). This indicates which serializer was used when storing the fields in the\ndatabase. `fingerprint` identifies which encryption key was used to encrypt the field. This can allow multiple to be\nchained together in order to handle multiple keys or even migrations (TBD). Finally, the `ciphertext` block is the\nencrypted value.\n\nThe `aes` serializer uses direct key encryption. It is recommended that the `aes` serializer not be used to encrypt\nvalues that may be repeated. This is because the `aes` serializer does not factor a unique seed in with each entry.\nInstead, it's expected that the values being encrypted are unique (for example, other encryption keys).\n\nThe `aes-gcm` serializer uses intermediary keys that are stored in the `encryption_keys` table to encrypt data across\nthe entire database. Unlike the `aes` serializer, the `aes-gcm` serializers is safe to use on values that may be\nrepeated. The key itself is encrypted using the `aes` serializer, making it easy to rotate the root key without needing\nto read, decrypt, and re-encrypt every field in the database. This makes rotations quick and strongly protects the core\nencryption keys from attackers.\n\n## Support\n\n| Driver                     | Supported | Notes                                              |\n|----------------------------|-----------|----------------------------------------------------|\n| github.com/glebarez/sqlite | ✅         | Support since day one.                             |\n| gorm.io/driver/postgres    | ✅         | -                                                  |\n| gorm.io/driver/mysql       | ❌         | https://github.com/mjpitz/gorm-encryption/issues/3 |\n| gorm.io/driver/sqlserver   | ✅         | -                                                  |\n\n## Usage\n\n```shell\ngo get go.pitz.tech/gorm/encryption\n```\n\n### Tagging fields for encryption\n\nGorm uses tags to communicate which serializer should be used when reading and writing the field to the database. This\nlibrary only supports using the `aes` and `aes-gcm` serializers on `[]byte` fields.\n\n```go\npackage main\n\ntype Model struct {\n\tUniqueValue    []byte `gorm:\"...;serializer:aes\"`\n\tNonUniqueValue []byte `gorm:\"...;serializer:aes-gcm\"`\n}\n```\n\n**A few notes...**\n\nFirst, when using fixed size `[]byte` fields, you'll need to consider the length added by the additional metadata of the\nencrypted fields. The equations below roughly communicate how much additional length will be needed.\n\n* `aes = data length + 50`\n* `aes-gcm = data length + 62`\n\nThe various lengths for the additional metadata are as follows:\n\n* prefix = 4 bytes\n* algorithm = 1 byte\n* separator = 1 byte\n* fingerprint = 43 bytes\n* separator = 1 byte\n* data = `x` bytes (aes) | `x` + 12 bytes (aes-gcm)\n\nSecond, you need to be mindful of how indexes are used in conjunction with encrypted fields. For example, if you're\nencrypting an `email_address` using `aes-gcm`, then you can't use a `unique` index on that field. You can however use a\nunique index on a semi-representative field such as an `email_hash`. Which can be in plaintext in the database. While\nyou could encrypt your `email_address` using the `aes` serializer, the field would require explicit rotation.\n\n### With database migrations\n\n```go\npackage main\n\nimport (\n\t\"go.pitz.tech/gorm/encryption\"\n\t\"gorm.io/gorm\"\n)\n\nfunc migrate(encryptionKey []byte) error {\n\tvar dialector gorm.Dialector\n\n\tdb, err := gorm.Open(dialector, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = encryption.Register(db, encryption.WithKey(encryptionKey), encryption.WithMigration())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn db.AutoMigrate(\n\t\t// your application models...\n\t)\n}\n```\n\n### Without database migrations\n\n```go\npackage main\n\nimport (\n\t\"go.pitz.tech/gorm/encryption\"\n\t\"gorm.io/gorm\"\n)\n\nfunc run(encryptionKey []byte) error {\n\tvar dialector gorm.Dialector\n\n\tdb, err := gorm.Open(dialector, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = encryption.Register(db, encryption.WithKey(encryptionKey))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// your business logic\n\n\treturn nil\n}\n```\n\n### Custom AES serializer\n\n```go\npackage main\n\nimport (\n\t\"go.pitz.tech/gorm/encryption\"\n\t\"go.pitz.tech/gorm/encryption/aes\"\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/schema\"\n)\n\nfunc run(customKey []byte) error {\n\tschema.RegisterSerializer(\"custom-aes\", aes.New(customKey))\n\t// now you have `serializer:custom-aes`\n\n\tvar dialector gorm.Dialector\n\n\tdb, err := gorm.Open(dialector, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// your business logic\n\n\treturn nil\n}\n```\n\n## Rotating keys\n\nThe code below hasn't been tested, but conveys the basic idea on how to rotate the primary encryption key.\n\n```go\npackage main\n\nimport (\n\t\"database/sql\"\n\n\t\"go.pitz.tech/gorm/encryption\"\n\t\"go.pitz.tech/gorm/encryption/database\"\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/schema\"\n)\n\nfunc rotate(oldKey, newKey []byte) error {\n\tvar dialector gorm.Dialector\n\n\tdb, err := gorm.Open(dialector, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// to do this in batches, you simply need to paginate the following block until you iterate through the entire table\n\n\terr = encryption.Register(db, encryption.WithKey(oldKey))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tallKeys := make([]database.Key, 0)\n\terr = db.Find(\u0026allKeys).Error\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = encryption.Register(db, encryption.WithKey(newKey))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn db.Transaction(func(txn *gorm.DB) error {\n\t\tfor _, key := range allKeys {\n\t\t\terr := txn.Save(key).Error\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\treturn nil\n\t})\n}\n```\n\n## License\n\n`MIT`. See [LICENSE](LICENSE) for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjpitz%2Fgorm-encryption","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmjpitz%2Fgorm-encryption","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmjpitz%2Fgorm-encryption/lists"}