安全是纸飞机的核心价值之一。本指南将帮助您了解纸飞机的安全机制,并提供最佳实践,确保您在使用纸飞机API时能够维护应用和用户数据的安全性。
安全是一个持续的过程,而不是一次性的任务。请定期检查和更新您的安全措施,以应对新出现的威胁。
作为纸飞机API的使用者,您也有责任确保应用的安全性:
纸飞机使用端到端加密技术保护所有消息,确保只有通信双方能够读取消息内容,即使是纸飞机服务器也无法访问。
纸飞机使用RSA-2048和Curve25519算法进行密钥交换,使用AES-256-GCM进行消息加密。每个用户都有一对公钥和私钥,公钥可以安全地分享给他人,而私钥则安全地存储在用户设备上。
当用户A向用户B发送消息时,用户A使用用户B的公钥加密消息密钥,然后使用该消息密钥加密实际消息内容。只有用户B的私钥能够解密消息密钥,从而解密消息内容。
用户可以通过验证安全码或扫描二维码来确认对方的身份和公钥的真实性,防止中间人攻击。
纸飞机SDK会自动处理所有加密和解密操作,您不需要手动实现加密逻辑。
纸飞机提供多种身份认证方式,确保只有授权用户能够访问API和用户数据。
| 认证方式 | 描述 | 适用场景 |
|---|---|---|
| 手机号+验证码 | 通过手机号接收验证码进行认证 | 移动应用、Web应用 |
| 二维码扫描 | 使用纸飞机移动应用扫描二维码进行认证 | Web应用、桌面应用 |
| 第三方授权 | 通过微信、QQ、Apple ID等第三方账号进行认证 | 移动应用、Web应用 |
| 生物识别 | 使用指纹或面容ID进行本地认证 | 移动应用 |
纸飞机支持多因素认证(MFA),为账户安全提供额外保障:
如果您的应用允许用户设置密码,请实施强密码策略,包括最小长度、复杂性要求等。
实施登录尝试限制,防止暴力破解攻击。例如,5次失败尝试后锁定账户或要求额外验证。
鼓励或要求用户启用多因素认证,特别是对于高风险操作。
监控并提醒用户异常登录活动,如从未知设备或位置登录。
不要在客户端代码中硬编码任何认证凭证,包括API密钥和客户端密钥。
纸飞机使用OAuth 2.0协议进行API认证,包括访问令牌(access token)和刷新令牌(refresh token)。
// 使用Keychain存储令牌
import Security
func saveTokenToKeychain(token: String, key: String) -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: token.data(using: .utf8)!,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
}
func getTokenFromKeychain(key: String) -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitOne
]
var dataTypeRef: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == errSecSuccess {
if let data = dataTypeRef as? Data,
let token = String(data: data, encoding: .utf8) {
return token
}
}
return nil
}
// 使用KeyStore存储令牌
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
object KeyStoreManager {
private const val KEYSTORE_NAME = "AndroidKeyStore"
private const val KEY_ALIAS = "paperplane_token_key"
private const val GCM_IV_LENGTH = 12
fun encryptToken(token: String): String {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, getOrCreateSecretKey())
val iv = cipher.iv
val encryptedBytes = cipher.doFinal(token.toByteArray(Charsets.UTF_8))
val combined = ByteArray(iv.size + encryptedBytes.size)
System.arraycopy(iv, 0, combined, 0, iv.size)
System.arraycopy(encryptedBytes, 0, combined, iv.size, encryptedBytes.size)
return Base64.encodeToString(combined, Base64.DEFAULT)
}
fun decryptToken(encryptedToken: String): String {
val combined = Base64.decode(encryptedToken, Base64.DEFAULT)
val iv = ByteArray(GCM_IV_LENGTH)
val encryptedBytes = ByteArray(combined.size - GCM_IV_LENGTH)
System.arraycopy(combined, 0, iv, 0, iv.size)
System.arraycopy(combined, iv.size, encryptedBytes, 0, encryptedBytes.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, getOrCreateSecretKey(), spec)
val decryptedBytes = cipher.doFinal(encryptedBytes)
return String(decryptedBytes, Charsets.UTF_8)
}
private fun getOrCreateSecretKey(): SecretKey {
val keyStore = KeyStore.getInstance(KEYSTORE_NAME)
keyStore.load(null)
keyStore.getEntry(KEY_ALIAS, null)?.let {
return (it as KeyStore.SecretKeyEntry).secretKey
}
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
KEYSTORE_NAME
)
keyGenerator.init(
KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(false)
.build()
)
return keyGenerator.generateKey()
}
}
// 使用HttpOnly Cookie和Secure标志
// 服务器端设置Cookie
res.cookie('access_token', accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // 生产环境使用HTTPS
sameSite: 'strict',
maxAge: 3600000 // 1小时
});
res.cookie('refresh_token', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 30 * 24 * 60 * 60 * 1000 // 30天
});
// 或者使用localStorage配合加密
import CryptoJS from 'crypto-js';
const SECRET_KEY = 'your-secret-key'; // 应该从环境变量获取
function saveToken(token, key) {
const encryptedToken = CryptoJS.AES.encrypt(token, SECRET_KEY).toString();
localStorage.setItem(key, encryptedToken);
}
function getToken(key) {
const encryptedToken = localStorage.getItem(key);
if (!encryptedToken) return null;
const bytes = CryptoJS.AES.decrypt(encryptedToken, SECRET_KEY);
return bytes.toString(CryptoJS.enc.Utf8);
}
永远不要在URL中传递令牌,这可能导致令牌泄露(例如,通过浏览器历史记录或服务器日志)。
保护存储在用户设备或服务器上的数据是应用安全的重要组成部分。
| 数据类型 | 示例 | 安全要求 |
|---|---|---|
| 高敏感度 | API密钥、密码、令牌 | 加密存储、严格访问控制 |
| 中敏感度 | 用户个人信息、聊天记录 | 加密存储、访问日志 |
| 低敏感度 | 公开信息、非个人数据 | 基本访问控制 |
只收集和存储必要的数据,遵循数据最小化原则。明确告知用户您收集的数据类型和用途。
当数据不再需要时,确保安全删除,包括清除缓存和临时文件。使用安全擦除方法,而不是简单地删除文件引用。
实施基于角色的访问控制(RBAC),确保用户只能访问其有权限的数据。使用最小权限原则配置数据库用户权限。
记录所有数据访问和修改操作,包括谁、何时、何处以及做了什么。这有助于检测和调查安全事件。
纸飞机SDK提供了内置的数据加密和安全存储功能,建议优先使用这些功能而不是自行实现。
保护应用与服务器之间的通信是防止中间人攻击和数据泄露的关键。
确保所有API请求都使用HTTPS协议,防止中间人攻击和数据窃听。配置服务器使用最新的TLS版本(TLS 1.2或更高)和安全的密码套件。
实施证书固定,防止攻击者使用伪造的证书进行中间人攻击。
// iOS示例(使用URLSession)
class CertificatePinningDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust,
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// 获取固定的证书数据
let pinnedCertData = getPinnedCertificateData()
// 获取服务器证书数据
let serverCertData = SecCertificateCopyData(certificate) as Data
// 比较证书
if serverCertData == pinnedCertData {
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
private func getPinnedCertificateData() -> Data {
// 从Bundle加载固定的证书
guard let certURL = Bundle.main.url(forResource: "paperplane_cert", withExtension: "cer"),
let certData = try? Data(contentsOf: certURL) else {
fatalError("Pinned certificate not found")
}
return certData
}
}
对于敏感操作,考虑实施请求签名机制,确保请求的完整性和真实性:
// 生成请求签名
function generateSignature(method, url, timestamp, body, secretKey) {
const data = `${method.toUpperCase()}|${url}|${timestamp}|${JSON.stringify(body)}`;
return CryptoJS.HmacSHA256(data, secretKey).toString(CryptoJS.enc.Hex);
}
// 发送请求时添加签名
async function sendSignedRequest(url, options = {}) {
const method = options.method || 'GET';
const body = options.body || {};
const timestamp = Date.now();
const secretKey = 'your-secret-key'; // 应该从安全存储获取
const signature = generateSignature(method, url, timestamp, body, secretKey);
const headers = {
...options.headers,
'X-Timestamp': timestamp,
'X-Signature': signature,
'X-API-Key': 'your-api-key'
};
return fetch(url, {
...options,
headers,
body: method !== 'GET' ? JSON.stringify(body) : undefined
});
}
避免在客户端代码中硬编码API密钥或签名密钥。考虑使用服务器代理或安全的密钥管理解决方案。
保护客户端应用免受逆向工程、篡改和其他客户端攻击是应用安全的重要方面。
// iOS越狱检测示例
func isDeviceJailbroken() -> Bool {
#if targetEnvironment(simulator)
return false
#else
// 检查常见的越狱文件和目录
let jailbreakPaths = [
"/Applications/Cydia.app",
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/bin/bash",
"/usr/sbin/sshd",
"/etc/apt",
"/private/var/lib/apt/"
]
for path in jailbreakPaths {
if FileManager.default.fileExists(atPath: path) {
return true
}
}
// 检查是否能写入受保护的系统目录
let systemPath = "/private/var/lib/apt/"
do {
try "test".write(toFile: systemPath + "jailbreak.txt", atomically: true, encoding: .utf8)
try FileManager.default.removeItem(atPath: systemPath + "jailbreak.txt")
return true
} catch {
// 正常情况下应该无法写入
}
return false
#endif
}
// Android Root检测示例
fun isDeviceRooted(): Boolean {
// 检查常见的Root文件和目录
val rootFiles = arrayOf(
"/system/app/Superuser.apk",
"/system/xbin/su",
"/system/bin/su",
"/system/sbin/su",
"/sbin/su",
"/vendor/bin/su",
"/etc/init.d/99SuperSUDaemon",
"/data/local/xbin/su",
"/data/local/bin/su"
)
for (path in rootFiles) {
if (File(path).exists()) {
return true
}
}
// 检查环境变量
try {
val env = System.getenv("PATH")
if (env != null) {
for (path in env.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
if (File(path + File.separator + "su").exists()) {
return true
}
}
}
} catch (e: Exception) {
// 忽略异常
}
return false
}
虽然这些检测措施可以增加攻击难度,但无法完全防止 determined 的攻击者。将这些措施作为多层防御策略的一部分,而不是唯一的安全保障。
如果您运行自己的服务器来与纸飞机API交互,确保服务器的安全性至关重要。
定期进行安全审计,包括:
考虑使用云服务提供商的托管安全服务,如AWS Security Hub、Azure Security Center或Google Cloud Security Command Center,这些服务可以帮助您更有效地管理服务器安全。
即使采取了所有预防措施,安全事件仍可能发生。制定和实施安全事件响应计划至关重要。
如果您发现与纸飞机API或SDK相关的安全漏洞,请按照以下步骤报告:
纸飞机尊重负责任的安全研究,并承诺不会对善意报告安全漏洞的研究人员采取法律行动。
确保您的应用符合相关的数据保护和隐私法规是法律要求,也是用户信任的基础。
| 法规 | 地区 | 主要要求 |
|---|---|---|
| GDPR | 欧盟 | 数据最小化、用户同意、数据主体权利、数据保护影响评估 |
| CCPA/CPRA | 加利福尼亚州 | 透明度、用户权利、数据安全、禁止歧视 |
| LGPD | 巴西 | 合法性基础、透明度、安全措施、数据主体权利 |
| 个人信息保护法 | 中国 | 告知同意、数据本地化、安全评估、儿童保护 |
创建清晰、易懂的隐私政策,明确说明:
实施有效的同意机制:
尊重和支持用户的数据主体权利:
对于高风险的数据处理活动,考虑进行数据保护影响评估(DPIA),包括:
法规要求可能因地区和行业而异。建议咨询法律顾问,确保您的应用符合所有适用的法规要求。
使用以下检查清单确保您的应用符合基本安全要求:
这只是基本的检查清单,实际的安全要求可能因应用类型、行业和地区而异。建议根据您的具体需求扩展此清单。
纸飞机使用端到端加密技术保护所有消息。这意味着只有通信双方能够读取消息内容,即使是纸飞机服务器也无法访问。加密过程在发送方设备上进行,解密过程在接收方设备上进行,使用双方的密钥对。
不需要。纸飞机SDK会自动处理所有加密和解密操作,您不需要手动实现加密逻辑。SDK确保所有消息在传输和存储过程中都是加密的。
API凭证(如API密钥、客户端密钥)应该安全存储,避免硬编码在客户端代码中。对于服务器端应用,可以使用环境变量或密钥管理服务。对于客户端应用,应该使用系统提供的安全存储机制,如iOS的Keychain或Android的KeyStore。
防止中间人攻击的主要措施包括:
如果发生用户数据泄露,您应该:
确保应用符合GDPR要求的关键步骤包括:
检测和防止应用被篡改的措施包括:
安全处理用户密码的最佳实践包括:
帮助我们改进文档,提供反馈或寻求支持