名称: sip-认证-安全 用户可调用: false 描述: 在实现SIP认证、安全机制和加密时使用。用于保护SIP服务器、客户端或代理。 允许工具:
- Bash
- Read
SIP认证与安全
掌握SIP认证机制(HTTP摘要认证)、TLS加密、SIPS以及构建安全VoIP应用的最佳实践。
HTTP摘要认证
挑战-响应流程
客户端 服务器
| |
| REGISTER(无凭证) |
|---------------------------------------->|
| |
| 401未授权 |
| WWW-Authenticate: Digest |
| realm="atlanta.com" |
| nonce="dcd98b7102dd..." |
| algorithm=MD5 |
| qop="auth" |
|<----------------------------------------|
| |
| REGISTER(带授权) |
| Authorization: Digest |
| username="alice" |
| realm="atlanta.com" |
| nonce="dcd98b7102dd..." |
| uri="sip:atlanta.com" |
| response="6629fae49393..." |
| algorithm=MD5 |
| qop=auth |
| nc=00000001 |
| cnonce="0a4f113b" |
|---------------------------------------->|
| |
| 200 OK |
|<----------------------------------------|
| |
摘要认证实现
import crypto from 'crypto';
interface DigestChallenge {
realm: string;
nonce: string;
algorithm: 'MD5' | 'SHA-256';
qop?: 'auth' | 'auth-int';
opaque?: string;
stale?: boolean;
}
interface DigestCredentials {
username: string;
realm: string;
nonce: string;
uri: string;
response: string;
algorithm: 'MD5' | 'SHA-256';
cnonce?: string;
nc?: string;
qop?: string;
opaque?: string;
}
class SipDigestAuth {
// 生成认证挑战(401/407响应)
static generateChallenge(realm: string): DigestChallenge {
return {
realm,
nonce: this.generateNonce(),
algorithm: 'MD5',
qop: 'auth',
opaque: this.generateOpaque()
};
}
// 创建WWW-Authenticate或Proxy-Authenticate头
static createChallengeHeader(challenge: DigestChallenge): string {
let header = `Digest realm="${challenge.realm}", ` +
`nonce="${challenge.nonce}", ` +
`algorithm=${challenge.algorithm}`;
if (challenge.qop) {
header += `, qop="${challenge.qop}"`;
}
if (challenge.opaque) {
header += `, opaque="${challenge.opaque}"`;
}
if (challenge.stale) {
header += `, stale=TRUE`;
}
return header;
}
// 计算认证响应
static calculateResponse(params: {
username: string;
password: string;
realm: string;
method: string;
uri: string;
nonce: string;
algorithm?: 'MD5' | 'SHA-256';
cnonce?: string;
nc?: string;
qop?: string;
body?: string;
}): string {
const algorithm = params.algorithm || 'MD5';
const hashFunc = algorithm === 'MD5' ? 'md5' : 'sha256';
// 计算A1 = MD5(username:realm:password)
const a1 = this.hash(
hashFunc,
`${params.username}:${params.realm}:${params.password}`
);
// 计算A2
let a2: string;
if (params.qop === 'auth-int') {
// A2 = MD5(method:uri:MD5(body))
const bodyHash = this.hash(hashFunc, params.body || '');
a2 = this.hash(hashFunc, `${params.method}:${params.uri}:${bodyHash}`);
} else {
// A2 = MD5(method:uri)
a2 = this.hash(hashFunc, `${params.method}:${params.uri}`);
}
// 计算响应
let response: string;
if (params.qop) {
// response = MD5(A1:nonce:nc:cnonce:qop:A2)
response = this.hash(
hashFunc,
`${a1}:${params.nonce}:${params.nc}:${params.cnonce}:${params.qop}:${a2}`
);
} else {
// response = MD5(A1:nonce:A2)
response = this.hash(hashFunc, `${a1}:${params.nonce}:${a2}`);
}
return response;
}
// 创建Authorization或Proxy-Authorization头
static createAuthorizationHeader(params: {
username: string;
password: string;
realm: string;
method: string;
uri: string;
nonce: string;
algorithm?: 'MD5' | 'SHA-256';
qop?: string;
opaque?: string;
}): string {
const algorithm = params.algorithm || 'MD5';
const cnonce = this.generateCnonce();
const nc = '00000001';
const qop = params.qop || 'auth';
const response = this.calculateResponse({
username: params.username,
password: params.password,
realm: params.realm,
method: params.method,
uri: params.uri,
nonce: params.nonce,
algorithm,
cnonce,
nc,
qop
});
let header = `Digest username="${params.username}", ` +
`realm="${params.realm}", ` +
`nonce="${params.nonce}", ` +
`uri="${params.uri}", ` +
`response="${response}", ` +
`algorithm=${algorithm}`;
if (qop) {
header += `, qop=${qop}, nc=${nc}, cnonce="${cnonce}"`;
}
if (params.opaque) {
header += `, opaque="${params.opaque}"`;
}
return header;
}
// 验证客户端凭证
static verifyCredentials(
credentials: DigestCredentials,
password: string,
method: string
): boolean {
const expectedResponse = this.calculateResponse({
username: credentials.username,
password,
realm: credentials.realm,
method,
uri: credentials.uri,
nonce: credentials.nonce,
algorithm: credentials.algorithm,
cnonce: credentials.cnonce,
nc: credentials.nc,
qop: credentials.qop
});
return credentials.response === expectedResponse;
}
// 解析Authorization/Proxy-Authorization头
static parseAuthorizationHeader(header: string): DigestCredentials | null {
if (!header.startsWith('Digest ')) {
return null;
}
const params: any = {};
const paramRegex = /(\w+)=(?:"([^"]+)"|([^,\s]+))/g;
let match;
while ((match = paramRegex.exec(header)) !== null) {
const key = match[1];
const value = match[2] || match[3];
params[key] = value;
}
return {
username: params.username,
realm: params.realm,
nonce: params.nonce,
uri: params.uri,
response: params.response,
algorithm: params.algorithm || 'MD5',
cnonce: params.cnonce,
nc: params.nc,
qop: params.qop,
opaque: params.opaque
};
}
private static hash(algorithm: string, data: string): string {
return crypto.createHash(algorithm).update(data).digest('hex');
}
private static generateNonce(): string {
// Nonce = Base64(timestamp:ETag:private-key)
const timestamp = Date.now();
const etag = crypto.randomBytes(16).toString('hex');
const privateKey = 'secret-server-key';
const nonce = `${timestamp}:${etag}:${privateKey}`;
return Buffer.from(nonce).toString('base64');
}
private static generateCnonce(): string {
return crypto.randomBytes(16).toString('hex');
}
private static generateOpaque(): string {
return crypto.randomBytes(16).toString('hex');
}
}
完整认证示例
class SipAuthenticatedClient {
private username: string;
private password: string;
private realm?: string;
private nonce?: string;
private opaque?: string;
constructor(username: string, password: string) {
this.username = username;
this.password = password;
}
// 发送带认证的REGISTER
async register(server: string): Promise<void> {
// 首次尝试无凭证
let response = await this.sendRegister(server);
if (response.statusCode === 401) {
// 从WWW-Authenticate头提取挑战
const challenge = this.parseChallenge(response.headers['www-authenticate']);
if (!challenge) {
throw new Error('无效的认证挑战');
}
this.realm = challenge.realm;
this.nonce = challenge.nonce;
this.opaque = challenge.opaque;
// 发送带凭证的REGISTER
response = await this.sendRegister(server, true);
}
if (response.statusCode === 200) {
console.log('注册成功');
} else {
throw new Error(`注册失败: ${response.statusCode}`);
}
}
private async sendRegister(
server: string,
withAuth: boolean = false
): Promise<any> {
const uri = `sip:${server}`;
const method = 'REGISTER';
let message = `${method} ${uri} SIP/2.0\r
Via: SIP/2.0/UDP client.example.com;branch=z9hG4bK${this.generateBranch()}\r
Max-Forwards: 70\r
To: <sip:${this.username}@${server}>\r
From: <sip:${this.username}@${server}>;tag=${this.generateTag()}\r
Call-ID: ${this.generateCallId()}\r
CSeq: 1 REGISTER\r
Contact: <sip:${this.username}@client.example.com>\r
Expires: 3600\r
`;
if (withAuth && this.realm && this.nonce) {
const authHeader = SipDigestAuth.createAuthorizationHeader({
username: this.username,
password: this.password,
realm: this.realm,
method,
uri,
nonce: this.nonce,
opaque: this.opaque
});
message += `Authorization: ${authHeader}\r
`;
}
message += 'Content-Length: 0\r
\r
';
// 发送消息并获取响应
return this.send(message);
}
private parseChallenge(header: string): DigestChallenge | null {
if (!header || !header.startsWith('Digest ')) {
return null;
}
const params: any = {};
const paramRegex = /(\w+)=(?:"([^"]+)"|([^,\s]+))/g;
let match;
while ((match = paramRegex.exec(header)) !== null) {
const key = match[1];
const value = match[2] || match[3];
params[key] = value;
}
return {
realm: params.realm,
nonce: params.nonce,
algorithm: params.algorithm || 'MD5',
qop: params.qop,
opaque: params.opaque
};
}
private generateBranch(): string {
return crypto.randomBytes(16).toString('hex');
}
private generateTag(): string {
return crypto.randomBytes(8).toString('hex');
}
private generateCallId(): string {
return `${crypto.randomBytes(16).toString('hex')}@client.example.com`;
}
private async send(message: string): Promise<any> {
// 实现取决于传输层
console.log('发送:', message);
return { statusCode: 401, headers: {} };
}
}
服务器端认证
带认证的注册服务器
interface UserCredentials {
username: string;
password: string;
domain: string;
}
class SipRegistrar {
private users: Map<string, UserCredentials> = new Map();
private registrations: Map<string, Registration> = new Map();
private nonces: Map<string, NonceInfo> = new Map();
private realm: string;
constructor(realm: string) {
this.realm = realm;
}
// 添加用户到数据库
addUser(username: string, password: string, domain: string): void {
this.users.set(username, { username, password, domain });
}
// 处理REGISTER请求
handleRegister(request: SipRequest): SipResponse {
const authHeader = request.headers['authorization'];
if (!authHeader) {
// 无凭证,发送挑战
return this.sendChallenge();
}
// 解析凭证
const credentials = SipDigestAuth.parseAuthorizationHeader(authHeader);
if (!credentials) {
return this.createResponse(400, '错误请求');
}
// 验证nonce
const nonceInfo = this.nonces.get(credentials.nonce);
if (!nonceInfo) {
// Nonce过期或无效,发送新挑战
return this.sendChallenge(true);
}
// 检查nonce计数以防止重放攻击
if (credentials.nc && parseInt(credentials.nc, 16) <= nonceInfo.nc) {
return this.createResponse(401, '未授权');
}
// 获取用户密码
const user = this.users.get(credentials.username);
if (!user) {
return this.createResponse(403, '禁止访问');
}
// 验证凭证
const valid = SipDigestAuth.verifyCredentials(
credentials,
user.password,
'REGISTER'
);
if (!valid) {
return this.createResponse(403, '禁止访问');
}
// 更新nonce计数
if (credentials.nc) {
nonceInfo.nc = parseInt(credentials.nc, 16);
}
// 注册联系人
const contact = request.headers['contact'];
const expires = parseInt(request.headers['expires'] || '3600');
this.registerContact(credentials.username, contact, expires);
return this.createResponse(200, 'OK');
}
private sendChallenge(stale: boolean = false): SipResponse {
const challenge = SipDigestAuth.generateChallenge(this.realm);
challenge.stale = stale;
// 存储nonce
this.nonces.set(challenge.nonce, {
nonce: challenge.nonce,
timestamp: Date.now(),
nc: 0
});
const response = this.createResponse(401, '未授权');
response.headers['www-authenticate'] =
SipDigestAuth.createChallengeHeader(challenge);
return response;
}
private registerContact(
username: string,
contact: string,
expires: number
): void {
const registration: Registration = {
username,
contact,
expires: Date.now() + expires * 1000
};
this.registrations.set(username, registration);
// 设置过期计时器
setTimeout(() => {
this.registrations.delete(username);
}, expires * 1000);
}
private createResponse(statusCode: number, reason: string): SipResponse {
return {
version: 'SIP/2.0',
statusCode,
reasonPhrase: reason,
headers: {} as any,
body: undefined
};
}
// 清理过期的nonces
cleanupNonces(): void {
const now = Date.now();
const maxAge = 300000; // 5分钟
for (const [nonce, info] of this.nonces.entries()) {
if (now - info.timestamp > maxAge) {
this.nonces.delete(nonce);
}
}
}
}
interface Registration {
username: string;
contact: string;
expires: number;
}
interface NonceInfo {
nonce: string;
timestamp: number;
nc: number;
}
interface SipRequest {
method: string;
requestUri: string;
version: string;
headers: any;
body?: string;
}
interface SipResponse {
version: string;
statusCode: number;
reasonPhrase: string;
headers: any;
body?: string;
}
TLS/SIPS实现
安全SIP(SIPS)设置
import tls from 'tls';
import fs from 'fs';
interface TlsConfig {
cert: string;
key: string;
ca?: string;
rejectUnauthorized?: boolean;
minVersion?: string;
ciphers?: string;
}
class SipTlsServer {
private server: tls.Server;
private config: TlsConfig;
constructor(config: TlsConfig) {
this.config = config;
this.server = this.createServer();
}
private createServer(): tls.Server {
const options: tls.TlsOptions = {
cert: fs.readFileSync(this.config.cert),
key: fs.readFileSync(this.config.key),
// 要求客户端证书以进行相互TLS
requestCert: true,
rejectUnauthorized: this.config.rejectUnauthorized !== false,
// 使用强TLS版本
minVersion: (this.config.minVersion as any) || 'TLSv1.2',
// 使用安全密码套件
ciphers: this.config.ciphers || [
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256',
'DHE-RSA-AES256-GCM-SHA384',
'DHE-RSA-AES128-GCM-SHA256'
].join(':')
};
if (this.config.ca) {
options.ca = fs.readFileSync(this.config.ca);
}
const server = tls.createServer(options, (socket) => {
this.handleConnection(socket);
});
return server;
}
private handleConnection(socket: tls.TLSSocket): void {
console.log('安全连接已建立');
// 验证客户端证书
if (socket.authorized) {
console.log('客户端证书已验证');
const cert = socket.getPeerCertificate();
console.log('客户端CN:', cert.subject.CN);
} else {
console.log('客户端证书未授权:', socket.authorizationError);
socket.destroy();
return;
}
socket.on('data', (data) => {
this.handleData(socket, data);
});
socket.on('error', (error) => {
console.error('套接字错误:', error);
});
socket.on('close', () => {
console.log('连接已关闭');
});
}
private handleData(socket: tls.TLSSocket, data: Buffer): void {
const message = data.toString();
console.log('收到:', message);
// 处理SIP消息
// 发送响应
}
listen(port: number, host: string = '0.0.0.0'): void {
this.server.listen(port, host, () => {
console.log(`SIP TLS服务器监听于 ${host}:${port}`);
});
}
close(): void {
this.server.close();
}
}
class SipTlsClient {
private config: TlsConfig;
constructor(config: TlsConfig) {
this.config = config;
}
connect(host: string, port: number): Promise<tls.TLSSocket> {
return new Promise((resolve, reject) => {
const options: tls.ConnectionOptions = {
host,
port,
cert: fs.readFileSync(this.config.cert),
key: fs.readFileSync(this.config.key),
rejectUnauthorized: this.config.rejectUnauthorized !== false,
minVersion: (this.config.minVersion as any) || 'TLSv1.2',
ciphers: this.config.ciphers
};
if (this.config.ca) {
options.ca = fs.readFileSync(this.config.ca);
}
const socket = tls.connect(options, () => {
if (socket.authorized) {
console.log('连接到服务器,证书已验证');
resolve(socket);
} else {
console.error('证书验证失败:', socket.authorizationError);
socket.destroy();
reject(new Error('证书验证失败'));
}
});
socket.on('error', (error) => {
reject(error);
});
});
}
async send(host: string, port: number, message: string): Promise<void> {
const socket = await this.connect(host, port);
socket.write(message);
socket.on('data', (data) => {
console.log('收到:', data.toString());
});
}
}
// 使用示例
const serverConfig: TlsConfig = {
cert: '/path/to/server-cert.pem',
key: '/path/to/server-key.pem',
ca: '/path/to/ca-cert.pem',
rejectUnauthorized: true
};
const server = new SipTlsServer(serverConfig);
server.listen(5061);
const clientConfig: TlsConfig = {
cert: '/path/to/client-cert.pem',
key: '/path/to/client-key.pem',
ca: '/path/to/ca-cert.pem'
};
const client = new SipTlsClient(clientConfig);
SRTP和媒体安全
SDP中的SRTP密钥交换
v=0
o=alice 2890844526 2890844526 IN IP4 pc33.atlanta.com
s=安全会话
c=IN IP4 pc33.atlanta.com
t=0 0
m=audio 49170 RTP/SAVP 0
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
a=rtpmap:0 PCMU/8000
SRTP实现
import crypto from 'crypto';
interface SrtpParams {
cryptoSuite: string;
keyParams: string;
sessionParams?: string;
}
class SrtpCrypto {
// 从SDP解析crypto属性
static parseCryptoAttribute(attr: string): SrtpParams | null {
// a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:base64key|lifetime|mkiValue
const match = attr.match(
/crypto:(\d+)\s+([^\s]+)\s+inline:([^\s|]+)(?:\|([^\s|]+))?(?:\|([^\s]+))?/
);
if (!match) {
return null;
}
const [, tag, cryptoSuite, keyParams, lifetime, mki] = match;
return {
cryptoSuite,
keyParams,
sessionParams: lifetime
};
}
// 为SDP生成crypto属性
static generateCryptoAttribute(tag: number = 1): string {
const cryptoSuite = 'AES_CM_128_HMAC_SHA1_80';
const masterKey = crypto.randomBytes(16); // 128位
const masterSalt = crypto.randomBytes(14); // 112位
// 连接密钥和盐
const keyMaterial = Buffer.concat([masterKey, masterSalt]);
const keyParams = keyMaterial.toString('base64');
// 会话参数
const lifetime = '2^20'; // 2^20数据包
const mki = '1:32'; // MKI值:长度
return `crypto:${tag} ${cryptoSuite} inline:${keyParams}|${lifetime}|${mki}`;
}
// 从主密钥派生会话密钥
static deriveSessionKeys(
masterKey: Buffer,
masterSalt: Buffer,
index: number
): {
encryptionKey: Buffer;
authKey: Buffer;
saltingKey: Buffer;
} {
// 根据RFC 3711进行密钥派生
const kdr = 0; // 密钥派生率(0 = 永不重新密钥)
// 计算key_id
const r = index / (2 ** kdr);
// 派生加密密钥
const encryptionKey = this.deriveKey(masterKey, masterSalt, 0x00, r);
// 派生认证密钥
const authKey = this.deriveKey(masterKey, masterSalt, 0x01, r);
// 派生加盐密钥
const saltingKey = this.deriveKey(masterKey, masterSalt, 0x02, r);
return { encryptionKey, authKey, saltingKey };
}
private static deriveKey(
masterKey: Buffer,
masterSalt: Buffer,
label: number,
index: number
): Buffer {
// PRF(masterKey, (masterSalt XOR (label || index)))
// 简化实现
const iv = Buffer.alloc(16);
masterSalt.copy(iv);
iv[7] ^= label;
const cipher = crypto.createCipheriv('aes-128-cbc', masterKey, iv);
const key = cipher.update(Buffer.alloc(16));
return key;
}
// 加密RTP数据包
static encryptRtp(
packet: Buffer,
encryptionKey: Buffer,
saltingKey: Buffer,
ssrc: number,
sequenceNumber: number
): Buffer {
// 提取RTP头(前12字节)
const header = packet.slice(0, 12);
// 提取负载
const payload = packet.slice(12);
// 从加盐密钥和数据包索引生成IV
const iv = this.generateIv(saltingKey, ssrc, sequenceNumber);
// 加密负载
const cipher = crypto.createCipheriv('aes-128-ctr', encryptionKey, iv);
const encryptedPayload = Buffer.concat([
cipher.update(payload),
cipher.final()
]);
// 连接头和加密负载
return Buffer.concat([header, encryptedPayload]);
}
// 生成认证标签
static generateAuthTag(
packet: Buffer,
authKey: Buffer
): Buffer {
const hmac = crypto.createHmac('sha1', authKey);
hmac.update(packet);
const tag = hmac.digest();
// 对AES_CM_128_HMAC_SHA1_80使用前10字节
return tag.slice(0, 10);
}
private static generateIv(
saltingKey: Buffer,
ssrc: number,
sequenceNumber: number
): Buffer {
const iv = Buffer.alloc(16);
// 复制加盐密钥
saltingKey.copy(iv);
// XOR with SSRC
iv.writeUInt32BE(iv.readUInt32BE(4) ^ ssrc, 4);
// XOR with 序列号
iv.writeUInt16BE(iv.readUInt16BE(14) ^ sequenceNumber, 14);
return iv;
}
}
安全最佳实践
输入验证和清理
class SipSecurityValidator {
// 验证SIP URI以防止注入攻击
static validateUri(uri: string): boolean {
// 检查有效的SIP URI格式
const sipUriRegex = /^sips?:[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+$/;
if (!sipUriRegex.test(uri)) {
return false;
}
// 检查危险字符
const dangerousChars = ['<', '>', '"', "'", ';', '&', '|', '`'];
for (const char of dangerousChars) {
if (uri.includes(char)) {
return false;
}
}
return true;
}
// 验证头值
static validateHeader(name: string, value: string): boolean {
// 检查CRLF注入
if (value.includes('\r') || value.includes('
')) {
return false;
}
// 验证特定头
switch (name.toLowerCase()) {
case 'content-length':
return /^\d+$/.test(value);
case 'max-forwards':
const maxForwards = parseInt(value);
return !isNaN(maxForwards) && maxForwards >= 0 && maxForwards <= 70;
case 'cseq':
return /^\d+\s+[A-Z]+$/.test(value);
default:
return true;
}
}
// 验证SDP以防止注入
static validateSdp(sdp: string): boolean {
const lines = sdp.split('
');
for (const line of lines) {
// 每行应为type=value
if (!line.match(/^[a-z]=.+$/)) {
return false;
}
// 检查危险内容
if (line.includes('<script>') || line.includes('javascript:')) {
return false;
}
}
return true;
}
// 速率限制以防止DoS
static checkRateLimit(
source: string,
maxRequests: number = 10,
windowMs: number = 1000
): boolean {
const now = Date.now();
const requests = this.requestCounts.get(source) || { count: 0, resetTime: now + windowMs };
if (now > requests.resetTime) {
// 重置窗口
requests.count = 1;
requests.resetTime = now + windowMs;
this.requestCounts.set(source, requests);
return true;
}
if (requests.count >= maxRequests) {
return false;
}
requests.count++;
this.requestCounts.set(source, requests);
return true;
}
private static requestCounts = new Map<string, { count: number; resetTime: number }>();
}
反欺骗措施
class SipAntiSpoofing {
// 验证Via头以检测欺骗
static validateVia(via: string, sourceIp: string, sourcePort: number): boolean {
// 解析Via头
const match = via.match(/SIP\/2.0\/(\w+)\s+([^;:]+)(?::(\d+))?/);
if (!match) {
return false;
}
const [, transport, host, port] = match;
// 检查接收参数是否匹配源
const receivedMatch = via.match(/;received=([^;]+)/);
if (receivedMatch) {
const received = receivedMatch[1];
if (received !== sourceIp) {
console.warn('Via接收参数不匹配:', received, '对比', sourceIp);
return false;
}
}
// 检查rport参数是否匹配源端口
const rportMatch = via.match(/;rport(?:=(\d+))?/);
if (rportMatch && rportMatch[1]) {
const rport = parseInt(rportMatch[1]);
if (rport !== sourcePort) {
console.warn('Via rport参数不匹配:', rport, '对比', sourcePort);
return false;
}
}
return true;
}
// 添加接收和rport参数到Via
static addReceivedRport(via: string, sourceIp: string, sourcePort: number): string {
let result = via;
// 添加接收参数(如果不存在)
if (!via.includes('received=')) {
result += `;received=${sourceIp}`;
}
// 添加rport参数值
if (via.includes('rport') && !via.includes('rport=')) {
result = result.replace(/rport/, `rport=${sourcePort}`);
} else if (!via.includes('rport')) {
result += `;rport=${sourcePort}`;
}
return result;
}
}
何时使用此技能
在构建需要以下功能的应用时使用sip-认证-安全:
- 用户认证和授权
- 安全SIP通信(SIPS/TLS)
- 受保护的媒体流(SRTP)
- 带认证的注册
- 代理认证
- 基于证书的认证
- 防止重放攻击
- 防御SIP特定威胁
- 符合安全标准
- 企业VoIP安全
最佳实践
- 始终使用摘要认证 - 切勿以明文发送密码
- 实现nonce过期 - 使用时间限制的nonce防止重放攻击
- 使用强哈希算法 - 可能时优先使用SHA-256而非MD5
- 验证nonce计数(nc) - 检测nonce生命周期内的重放攻击
- 生成密码学随机值 - 对nonce、标签等使用crypto.randomBytes()
- 使用TLS进行信令 - 使用TLS(SIPS)加密SIP消息
- 使用SRTP进行媒体 - 使用SRTP加密RTP流
- 实现相互TLS - 要求客户端证书以进行服务器认证
- 验证所有输入 - 清理URI、头和SDP内容
- 添加接收/rport参数 - 防止Via头欺骗
- 实现速率限制 - 防止DoS和暴力攻击
- 使用不透明值 - 帮助检测篡改的认证响应
- 支持过时nonce - 允许客户端使用新nonce重试
- 记录认证失败 - 监控安全事件
- 定期轮换主密钥 - 限制受损密钥的暴露
常见陷阱
- 存储明文密码 - 存储前始终哈希密码
- 未验证nonce新鲜度 - 允许重放攻击
- 弱nonce生成 - 可预测的nonce危及安全
- 缺少qop参数 - 降低安全性,允许更易攻击
- 未检查nc递增 - 错过重放攻击尝试
- 接受自签名证书 - 为中间人攻击打开大门
- 使用弱密码套件 - 损害TLS安全
- 未验证证书链 - 接受无效证书
- 硬编码凭证 - 生产中的安全漏洞
- 无速率限制 - 易受DoS和暴力攻击
- 缺少Content-Length验证 - 启用缓冲区溢出攻击
- 未清理SDP - 易受注入攻击
- 信任Via头 - 启用IP欺骗攻击
- 在生产中使用MD5 - 已知漏洞,使用SHA-256
- 未实现SRTP - 暴露媒体于窃听