You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 21:22:50