如何在Unity项目中安全存储Gmail SMTP凭据以符合安全规范?
Unity Gmail SMTP凭据安全存储解决方案
问题背景
开发的Unity资源因直接在项目中存储Gmail SMTP凭据(含App Password)被驳回,当前代码将凭据作为序列化字段硬编码,构建后的包中凭据可被轻易反编译提取,违反安全规范:
[SerializeField] private string ReceiverMail = "ReceiverMail@gmail.com"; [SerializeField] private string SenderMail = "SenderMail@gmail.com"; [SerializeField] private string appPassword = "SenderMailAppPassword(dfkfjjfkjgmnfk)"; private async void SendMail() { // Code using email credentials... smtpServer.Credentials = new System.Net.NetworkCredential(SenderMail, appPassword) as ICredentialsByHost; // More code... }
可行解决方案
1. 后端中转服务(推荐)
核心逻辑:客户端不存储任何敏感凭据,所有邮件发送请求转发至自己的后端服务器,由后端完成SMTP认证和邮件发送,彻底隔绝客户端与凭据的接触。
实现步骤
- 搭建后端接口(可选用Node.js、Python Flask、ASP.NET等),接口接收邮件内容、收件人等参数,后端内部通过安全存储的凭据调用Gmail SMTP发送邮件。
- Unity客户端通过HTTPS请求将邮件数据发送到后端接口,全程不处理敏感信息。
示例代码
后端(Node.js + Nodemailer):
const express = require('express'); const nodemailer = require('nodemailer'); const app = express(); app.use(express.json()); // 凭据通过环境变量安全存储,避免硬编码 const transporter = nodemailer.createTransport({ service: 'gmail', auth: { user: process.env.GMAIL_USER, pass: process.env.GMAIL_APP_PASSWORD } }); app.post('/send-email', async (req, res) => { const { to, subject, text } = req.body; const mailOptions = { from: process.env.GMAIL_USER, to, subject, text }; try { await transporter.sendMail(mailOptions); res.status(200).send('邮件发送成功'); } catch (error) { res.status(500).send('邮件发送失败:' + error.message); } }); app.listen(3000, () => console.log('后端服务运行在3000端口'));
Unity客户端:
using UnityEngine; using UnityEngine.Networking; private async void SendMailViaBackend(string receiver, string subject, string content) { var mailData = new MailData { to = receiver, subject = subject, text = content }; string json = JsonUtility.ToJson(mailData); using (UnityWebRequest request = UnityWebRequest.Post("https://your-backend-domain.com/send-email", "")) { byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(json); request.uploadHandler = new UploadHandlerRaw(bodyRaw); request.downloadHandler = new DownloadHandlerBuffer(); request.SetRequestHeader("Content-Type", "application/json"); await request.SendWebRequest(); if (request.result != UnityWebRequest.Result.Success) { Debug.LogError("邮件发送失败:" + request.error); } else { Debug.Log("邮件发送成功"); } } } [System.Serializable] private class MailData { public string to; public string subject; public string text; }
2. 加密本地存储凭据
若必须在客户端存储凭据,需对凭据进行高强度加密,避免明文暴露。注意:此方式仍存在被破解风险,仅适合低安全要求场景。
实现要点
- 使用
PlayerPrefs结合AES加密存储凭据,密钥不能硬编码,可通过设备唯一标识符(SystemInfo.deviceUniqueIdentifier)结合动态盐值(如从后端获取)生成。
示例代码
using System; using System.Security.Cryptography; using System.Text; using UnityEngine; public class CredentialManager : MonoBehaviour { private string encryptionKey; private void Awake() { // 结合设备标识与后端下发的盐值生成密钥,示例仅作参考 string deviceId = SystemInfo.deviceUniqueIdentifier; string dynamicSalt = FetchSaltFromBackend(); // 从后端请求动态盐值 encryptionKey = HashString(deviceId + dynamicSalt).Substring(0, 32); // AES-256需要32位密钥 } private string FetchSaltFromBackend() { // 实现从后端获取动态盐值的逻辑,此处省略 return "your-dynamic-salt-from-backend"; } private string HashString(string input) { using (SHA256 sha256 = SHA256.Create()) { byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input)); StringBuilder builder = new StringBuilder(); foreach (byte b in bytes) builder.Append(b.ToString("x2")); return builder.ToString(); } } public void SaveCredentials(string email, string appPassword) { string encryptedEmail = EncryptString(email, encryptionKey); string encryptedPassword = EncryptString(appPassword, encryptionKey); PlayerPrefs.SetString("EncryptedEmail", encryptedEmail); PlayerPrefs.SetString("EncryptedPassword", encryptedPassword); PlayerPrefs.Save(); } public (string email, string appPassword) LoadCredentials() { if (!PlayerPrefs.HasKey("EncryptedEmail") || !PlayerPrefs.HasKey("EncryptedPassword")) return (null, null); string email = DecryptString(PlayerPrefs.GetString("EncryptedEmail"), encryptionKey); string password = DecryptString(PlayerPrefs.GetString("EncryptedPassword"), encryptionKey); return (email, password); } private string EncryptString(string plainText, string key) { byte[] iv = new byte[16]; byte[] array; using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(key); aes.IV = iv; ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream()) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { using (System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(cryptoStream)) { streamWriter.Write(plainText); } array = memoryStream.ToArray(); } } } return Convert.ToBase64String(array); } private string DecryptString(string cipherText, string key) { byte[] iv = new byte[16]; byte[] buffer = Convert.FromBase64String(cipherText); using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(key); aes.IV = iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(buffer)) { using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { using (System.IO.StreamReader streamReader = new System.IO.StreamReader(cryptoStream)) { return streamReader.ReadToEnd(); } } } } } }
3. OAuth 2.0认证替代App Password
Gmail支持OAuth 2.0认证,无需存储App Password,通过临时访问令牌完成SMTP认证。适合需要用户自主授权发送邮件的场景,不适合后台自动发送需求。
实现步骤
- 在Google Cloud Console创建OAuth应用,配置SMTP权限范围(
https://mail.google.com/)。 - Unity客户端引导用户完成OAuth授权流程,获取访问令牌,用令牌替代密码进行SMTP认证。
示例代码片段
// 使用OAuth访问令牌设置SMTP凭据 var oauthCredentials = new System.Net.NetworkCredential("", accessToken); smtpServer.Credentials = oauthCredentials; smtpServer.EnableSsl = true; var mailMessage = new System.Net.Mail.MailMessage(senderMail, receiverMail, subject, content); smtpServer.Send(mailMessage);
总结
- 优先选择后端中转方案:彻底隔绝客户端与敏感凭据的接触,安全性最高,完全符合Unity审核要求。
- 加密本地存储仅为折中方案,仍存在被破解风险,需谨慎使用。
- OAuth 2.0适合用户自主授权场景,不适用于后台自动发送邮件的需求。
内容的提问来源于stack exchange,提问作者Ch.Muhammad.Bin.khalid
相关产品推荐
相关产品推荐

