๐ PGP & Tor Integration in MyZubster
Complete technical guide to privacy, encryption, and anonymity in the MyZubster ecosystem
๐ What is PGP & Tor Integration?
MyZubster integrates two core privacy technologies:
๐ก๏ธ PGP (Pretty Good Privacy)
End-to-end encryption for messages between users
Digital signatures for order integrity and authentication
Key management for buyers, sellers, and arbitrators
Verification of user identity and order authenticity
๐ง Tor (The Onion Router)
Anonymity for users (hide IP addresses)
Censorship resistance โ access from anywhere
Orbot integration โ Tor on Android
Hidden services โ optional .onion addresses
๐งฉ Architecture Overview
text
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MyZubster Privacy Layer โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PGP Encryption Layer โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โ โ Key Pair โ โ Encrypt โ โ Sign โ โ โ
โ โ โ Generation โ โ Messages โ โ Messages โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โ โ Decrypt โ โ Verify โ โ Key Server โ โ โ
โ โ โ Messages โ โ Signatures โ โ (Public) โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Tor Anonymity Layer โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โ โ Tor Proxy โ โ Onion โ โ Orbot โ โ โ
โ โ โ (SOCKS5) โ โ Routing โ โ (Android) โ โ โ
โ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ Complete File Structure
text
marketplace/
โโโ services/
โ โโโ pgpService.js # PGP encryption/decryption
โ โโโ torService.js # Tor proxy configuration
โโโ routes/
โ โโโ pgp.js # PGP key management routes
โ โโโ messages.js # Encrypted messaging routes
โโโ models/
โ โโโ User.js # PGP public key storage
โ โโโ Message.js # Encrypted message storage
โโโ middleware/
โ โโโ tor.js # Tor detection middleware
โโโ utils/
โ โโโ pgpUtils.js # PGP helper functions
โ โโโ torUtils.js # Tor helper functions
โโโ config/
โโโ tor.js # Tor configuration
๐ง Complete Code Walkthrough
- PGP Service (services/pgpService.js)
File: marketplace/services/pgpService.js
javascript
const openpgp = require('openpgp');
const { createHash } = require('crypto');
// ============================================
// GENERATE PGP KEY PAIR
// ============================================
const generateKeyPair = async (name, email, passphrase) => {
try {
const { privateKey, publicKey } = await openpgp.generateKey({
type: 'rsa',
rsaBits: 4096,
userIDs: [{ name, email }],
passphrase: passphrase,
format: 'armored'
});
return {
privateKey: privateKey,
publicKey: publicKey,
fingerprint: await getFingerprint(publicKey)
};
} catch (error) {
console.error('โ Error generating PGP keys:', error);
throw error;
}
};
// ============================================
// GET FINGERPRINT FROM PUBLIC KEY
// ============================================
const getFingerprint = async (publicKeyArmored) => {
try {
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
return publicKey.getFingerprint();
} catch (error) {
console.error('โ Error getting fingerprint:', error);
return null;
}
};
// ============================================
// ENCRYPT MESSAGE
// ============================================
const encryptMessage = async (message, publicKeyArmored) => {
try {
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({ text: message }),
encryptionKeys: publicKey,
format: 'armored'
});
return encrypted;
} catch (error) {
console.error('โ Error encrypting message:', error);
throw error;
}
};
// ============================================
// DECRYPT MESSAGE
// ============================================
const decryptMessage = async (encryptedMessage, privateKeyArmored, passphrase) => {
try {
const privateKey = await openpgp.readPrivateKey({
armoredKey: privateKeyArmored
});
const decrypted = await openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: encryptedMessage }),
decryptionKeys: privateKey,
format: 'armored'
});
return decrypted.data;
} catch (error) {
console.error('โ Error decrypting message:', error);
throw error;
}
};
// ============================================
// SIGN MESSAGE
// ============================================
const signMessage = async (message, privateKeyArmored, passphrase) => {
try {
const privateKey = await openpgp.readPrivateKey({
armoredKey: privateKeyArmored
});
const signed = await openpgp.sign({
message: await openpgp.createMessage({ text: message }),
signingKeys: privateKey,
format: 'armored'
});
return signed;
} catch (error) {
console.error('โ Error signing message:', error);
throw error;
}
};
// ============================================
// VERIFY SIGNATURE
// ============================================
const verifySignature = async (signedMessage, publicKeyArmored) => {
try {
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
const verificationResult = await openpgp.verify({
message: await openpgp.readMessage({ armoredMessage: signedMessage }),
verificationKeys: publicKey
});
const { verified } = verificationResult.signatures[0];
await verified; // throw if invalid signature
return true;
} catch (error) {
console.error('โ Error verifying signature:', error);
return false;
}
};
// ============================================
// SIGN ORDER (for escrow)
// ============================================
const signOrder = async (orderData, privateKeyArmored, passphrase) => {
const message = JSON.stringify(orderData);
const signature = await signMessage(message, privateKeyArmored, passphrase);
return signature;
};
// ============================================
// VERIFY ORDER SIGNATURE
// ============================================
const verifyOrderSignature = async (orderData, signature, publicKeyArmored) => {
const message = JSON.stringify(orderData);
const signedMessage = -----BEGIN PGP SIGNED MESSAGE-----\n\n${message}\n${signature};
return await verifySignature(signedMessage, publicKeyArmored);
};
module.exports = {
generateKeyPair,
getFingerprint,
encryptMessage,
decryptMessage,
signMessage,
verifySignature,
signOrder,
verifyOrderSignature
};
- PGP Routes (routes/pgp.js)
File: marketplace/routes/pgp.js
javascript
const express = require('express');
const router = express.Router();
const { User } = require('../models');
const auth = require('../middleware/auth');
const {
generateKeyPair,
getFingerprint,
encryptMessage,
decryptMessage,
signMessage,
verifySignature
} = require('../services/pgpService');
// ============================================
// POST /api/pgp/generate
// Generate PGP key pair
// ============================================
router.post('/generate', auth, async (req, res) => {
try {
const { passphrase } = req.body;
if (!passphrase) {
return res.status(400).json({ error: 'Passphrase required' });
}
const user = await User.findByPk(req.user.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Generate key pair
const { privateKey, publicKey, fingerprint } = await generateKeyPair(
user.name || user.username || 'MyZubster User',
user.email,
passphrase
);
// Update user with PGP keys
user.pgpPublicKey = publicKey;
user.pgpPrivateKey = privateKey; // Encrypted with passphrase
user.pgpFingerprint = fingerprint;
await user.save();
// Return public key only (keep private key secure)
res.json({
success: true,
publicKey: publicKey,
fingerprint: fingerprint,
message: 'PGP keys generated successfully. Keep your private key and passphrase safe!'
});
} catch (error) {
console.error('โ Error generating PGP keys:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// GET /api/pgp/public-key/:userId
// Get user's public PGP key
// ============================================
router.get('/public-key/:userId', async (req, res) => {
try {
const user = await User.findByPk(req.params.userId, {
attributes: ['id', 'username', 'pgpPublicKey', 'pgpFingerprint']
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
if (!user.pgpPublicKey) {
return res.status(404).json({ error: 'User has no PGP key' });
}
res.json({
userId: user.id,
username: user.username,
fingerprint: user.pgpFingerprint,
publicKey: user.pgpPublicKey
});
} catch (error) {
console.error('โ Error fetching public key:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// POST /api/pgp/encrypt
// Encrypt message for recipient
// ============================================
router.post('/encrypt', auth, async (req, res) => {
try {
const { recipientUserId, message } = req.body;
if (!recipientUserId || !message) {
return res.status(400).json({ error: 'Recipient and message required' });
}
// Get recipient's public key
const recipient = await User.findByPk(recipientUserId, {
attributes: ['id', 'pgpPublicKey']
});
if (!recipient) {
return res.status(404).json({ error: 'Recipient not found' });
}
if (!recipient.pgpPublicKey) {
return res.status(400).json({ error: 'Recipient has no PGP key' });
}
// Encrypt message
const encrypted = await encryptMessage(message, recipient.pgpPublicKey);
res.json({
success: true,
encryptedMessage: encrypted
});
} catch (error) {
console.error('โ Error encrypting message:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// POST /api/pgp/decrypt
// Decrypt message
// ============================================
router.post('/decrypt', auth, async (req, res) => {
try {
const { encryptedMessage, passphrase } = req.body;
if (!encryptedMessage || !passphrase) {
return res.status(400).json({ error: 'Encrypted message and passphrase required' });
}
const user = await User.findByPk(req.user.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
if (!user.pgpPrivateKey) {
return res.status(400).json({ error: 'You have no PGP private key' });
}
// Decrypt message
const decrypted = await decryptMessage(
encryptedMessage,
user.pgpPrivateKey,
passphrase
);
res.json({
success: true,
decryptedMessage: decrypted
});
} catch (error) {
console.error('โ Error decrypting message:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// POST /api/pgp/sign
// Sign a message
// ============================================
router.post('/sign', auth, async (req, res) => {
try {
const { message, passphrase } = req.body;
if (!message || !passphrase) {
return res.status(400).json({ error: 'Message and passphrase required' });
}
const user = await User.findByPk(req.user.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
if (!user.pgpPrivateKey) {
return res.status(400).json({ error: 'You have no PGP private key' });
}
// Sign message
const signature = await signMessage(message, user.pgpPrivateKey, passphrase);
res.json({
success: true,
signature: signature
});
} catch (error) {
console.error('โ Error signing message:', error);
res.status(500).json({ error: error.message });
}
});
// ============================================
// POST /api/pgp/verify
// Verify signature
// ============================================
router.post('/verify', async (req, res) => {
try {
const { signedMessage, userId } = req.body;
if (!signedMessage || !userId) {
return res.status(400).json({ error: 'Signed message and userId required' });
}
// Get user's public key
const user = await User.findByPk(userId, {
attributes: ['id', 'pgpPublicKey']
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
if (!user.pgpPublicKey) {
return res.status(400).json({ error: 'User has no PGP key' });
}
// Verify signature
const isValid = await verifySignature(signedMessage, user.pgpPublicKey);
res.json({
success: true,
verified: isValid
});
} catch (error) {
console.error('โ Error verifying signature:', error);
res.status(500).json({ error: error.message });
}
});
module.exports = router;
- Message Model with Encryption (models/Message.js)
File: marketplace/models/Message.js
javascript
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Message = sequelize.define('Message', {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
senderId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
recipientId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
orderId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'Orders',
key: 'id'
}
},
// Encrypted content
encryptedContent: {
type: DataTypes.TEXT,
allowNull: false
},
// Signature for authenticity
signature: {
type: DataTypes.TEXT,
allowNull: true
},
// Encryption metadata
encryptionFingerprint: {
type: DataTypes.STRING,
allowNull: true
},
isRead: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
readAt: {
type: DataTypes.DATE,
allowNull: true
},
deliveredAt: {
type: DataTypes.DATE,
allowNull: true
}
}, {
timestamps: true,
tableName: 'Messages'
});
// Associations
Message.associate = (models) => {
Message.belongsTo(models.User, { as: 'sender', foreignKey: 'senderId' });
Message.belongsTo(models.User, { as: 'recipient', foreignKey: 'recipientId' });
Message.belongsTo(models.Order, { foreignKey: 'orderId' });
};
return Message;
};
- Tor Service (services/torService.js)
File: marketplace/services/torService.js
javascript
const axios = require('axios');
const SocksProxyAgent = require('socks-proxy-agent');
const dns = require('dns');
const { promisify } = require('util');
const TOR_PROXY = process.env.TOR_PROXY || 'socks5://localhost:9050';
const TOR_ENABLED = process.env.TOR_ENABLED === 'true';
// ============================================
// CHECK IF TOR IS RUNNING
// ============================================
const isTorRunning = async () => {
try {
const agent = new SocksProxyAgent(TOR_PROXY);
await axios.get('https://check.torproject.org/api/ip', {
httpAgent: agent,
httpsAgent: agent,
timeout: 5000
});
return true;
} catch (error) {
return false;
}
};
// ============================================
// GET TOR IP ADDRESS
// ============================================
const getTorIp = async () => {
try {
const agent = new SocksProxyAgent(TOR_PROXY);
const response = await axios.get('https://check.torproject.org/api/ip', {
httpAgent: agent,
httpsAgent: agent,
timeout: 10000
});
return response.data;
} catch (error) {
console.error('โ Error getting Tor IP:', error);
return null;
}
};
// ============================================
// CHECK IF USER IS USING TOR
// ============================================
const isUsingTor = (req) => {
// Check for Tor headers
const xForwardedFor = req.headers['x-forwarded-for'] || '';
const via = req.headers['via'] || '';
const userAgent = req.headers['user-agent'] || '';
// Tor Browser user agent pattern
const isTorBrowser = userAgent.includes('Tor') ||
userAgent.includes('torbrowser') ||
userAgent.includes('SecurityToken');
// Check for Tor exit node headers
const isTorExit = via.toLowerCase().includes('tor') ||
xForwardedFor.includes('.onion');
return isTorBrowser || isTorExit;
};
// ============================================
// GET ONION ADDRESS FOR SERVICE
// ============================================
const getOnionAddress = async (serviceUrl) => {
try {
const agent = new SocksProxyAgent(TOR_PROXY);
const response = await axios.get(${serviceUrl}/api/onion-address, {
httpAgent: agent,
httpsAgent: agent,
timeout: 10000
});
return response.data.onionAddress;
} catch (error) {
console.error('โ Error getting onion address:', error);
return null;
}
};
// ============================================
// RESOLVE .ONION ADDRESS
// ============================================
const resolveOnionAddress = async (onionAddress) => {
try {
// Use Tor's DNS resolver
const agent = new SocksProxyAgent(TOR_PROXY);
const resolver = new dns.Resolver({
timeout: 5000,
tries: 2
});
// .onion addresses are handled by Tor proxy
return onionAddress;
} catch (error) {
console.error('โ Error resolving onion address:', error);
return null;
}
};
// ============================================
// TOR PROXY MIDDLEWARE
// ============================================
const torMiddleware = (req, res, next) => {
req.isTor = isUsingTor(req);
req.torIp = req.isTor ? req.ip : null;
next();
};
// ============================================
// TOR REQUEST HELPER
// ============================================
const torRequest = async (url, options = {}) => {
try {
const agent = new SocksProxyAgent(TOR_PROXY);
const response = await axios({
url,
...options,
httpAgent: agent,
httpsAgent: agent,
timeout: options.timeout || 15000
});
return response;
} catch (error) {
console.error('โ Error making Tor request:', error);
throw error;
}
};
module.exports = {
TOR_ENABLED,
TOR_PROXY,
isTorRunning,
getTorIp,
isUsingTor,
getOnionAddress,
resolveOnionAddress,
torMiddleware,
torRequest
};
- Tor Middleware (middleware/tor.js)
File: marketplace/middleware/tor.js
javascript
const { isUsingTor, TOR_ENABLED } = require('../services/torService');
// ============================================
// ENFORCE TOR CONNECTION
// ============================================
const enforceTor = (req, res, next) => {
if (!TOR_ENABLED) {
return next();
}
const isTor = isUsingTor(req);
if (!isTor) {
return res.status(403).json({
error: 'This endpoint requires Tor connection',
message: 'Please access via Tor Browser or Orbot'
});
}
next();
};
// ============================================
// TOR LOGGING MIDDLEWARE
// ============================================
const torLogging = (req, res, next) => {
const isTor = isUsingTor(req);
const ip = req.ip || req.connection.remoteAddress;
console.log(๐ Request from: ${ip} (Tor: ${isTor ? 'โ
' : 'โ'}));
next();
};
// ============================================
// GET TOR STATUS
// ============================================
const getTorStatus = async () => {
const isRunning = await isTorRunning();
return {
enabled: TOR_ENABLED,
running: isRunning,
proxy: TOR_PROXY
};
};
module.exports = {
enforceTor,
torLogging,
getTorStatus
};
- User Model with PGP (models/User.js - updated)
File: marketplace/models/User.js (add these fields)
javascript
// Add to existing User model
pgpPublicKey: {
type: DataTypes.TEXT,
allowNull: true
},
pgpPrivateKey: {
type: DataTypes.TEXT,
allowNull: true
},
pgpFingerprint: {
type: DataTypes.STRING(40),
allowNull: true
},
torPreference: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
lastTorConnection: {
type: DataTypes.DATE,
allowNull: true
}
- Tor Configuration (config/tor.js)
File: marketplace/config/tor.js
javascript
const { TOR_ENABLED, TOR_PROXY } = process.env;
module.exports = {
enabled: TOR_ENABLED === 'true',
proxy: TOR_PROXY || 'socks5://localhost:9050',
timeout: 15000,
retries: 3,
// Orbot settings for Android
orbot: {
enabled: process.env.ORBOT_ENABLED === 'true',
port: parseInt(process.env.ORBOT_PORT) || 9050,
host: process.env.ORBOT_HOST || 'localhost'
},
// Hidden service settings
hiddenService: {
enabled: process.env.ONION_SERVICE_ENABLED === 'true',
hostname: process.env.ONION_HOSTNAME || '',
port: parseInt(process.env.ONION_PORT) || 80
}
};
๐ง Environment Variables
File: marketplace/.env (update)
env
Existing variables...
PORT=4000
NODE_ENV=development
DATABASE_URL=sqlite:./database.sqlite
JWT_SECRET=your_jwt_secret
PGP Settings
PGP_ENABLED=true
PGP_KEY_DIR=./keys
PGP_DEFAULT_PASSPHRASE=change_me_123
Tor Settings
TOR_ENABLED=true
TOR_PROXY=socks5://localhost:9050
ORBOT_ENABLED=true
ORBOT_PORT=9050
Onion Service
ONION_SERVICE_ENABLED=false
ONION_HOSTNAME=myzubster.onion
ONION_PORT=80
๐งช Test Commands
Generate PGP Keys
bash
curl -X POST http://localhost:4000/api/pgp/generate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"passphrase":"my_secure_passphrase"}'
Get User's Public Key
bash
curl http://localhost:4000/api/pgp/public-key/1
Encrypt Message
bash
curl -X POST http://localhost:4000/api/pgp/encrypt \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"recipientUserId":2,"message":"Hello, this is a secret message!"}'
Decrypt Message
bash
curl -X POST http://localhost:4000/api/pgp/decrypt \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"encryptedMessage":"-----BEGIN PGP MESSAGE-----...", "passphrase":"my_secure_passphrase"}'
Sign Message
bash
curl -X POST http://localhost:4000/api/pgp/sign \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message":"I agree to the terms","passphrase":"my_secure_passphrase"}'
Verify Signature
bash
curl -X POST http://localhost:4000/api/pgp/verify \
-H "Content-Type: application/json" \
-d '{"signedMessage":"-----BEGIN PGP SIGNED MESSAGE-----...","userId":2}'
Check Tor Status
bash
curl http://localhost:4000/api/tor/status
๐ Security Best Practices
Practice Implementation
PGP Key Generation 4096-bit RSA keys
Private Key Storage Encrypted with passphrase
Message Encryption End-to-end encryption
Signature Verification Digital signatures for authenticity
Tor Connection SOCKS5 proxy with fallback
Orbot Integration Android Tor support
Onion Services Optional hidden services
๐ Troubleshooting
"PGP keys not generated"
bash
Check if PGP_ENABLED is true
echo $PGP_ENABLED
"Tor connection failed"
bash
Check if Tor is running
systemctl status tor
Or start Tor
tor &
"Orbot not connecting"
bash
Check Orbot settings
adb shell netstat -an | grep 9050
๐ Resources
OpenPGP.js: openpgpjs.org
Tor Project: torproject.org
Orbot: orbot.app
โ Next Steps
Test PGP on mobile app โ React Native encryption
Implement Onion services โ .onion addresses for API
Add key revocation โ Revoke compromised keys
Improve UX โ Passphrase management on mobile
Write tests โ Unit tests for PGP/Tor integration
Built with โค๏ธ for privacy, freedom, and decentralization.
Happy coding! ๐
Top comments (0)