Recommendation: GSI SNEP (v1)
Structured JSON encapsulation for CLEP and other messages for signing messages with RSA/HMAC
The Global Scripting Institute (GSI) is an informal organization of Second Life® users that design and test standards for efficient, flexible, and readable scripts in Second Life. "Second Life®" and "Second Life Grid™" are trademarks of Linden Research, Inc., d/b/a Linden Lab. The Global Scripting Institute and its catalog are not affiliated with or sponsored by Linden Research.
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
The Signed Network Encapsulation Protocol (SNEP) is a protocol for arbitrary messages to be encapsulated in-place and signed for message/source integrity.
Lua-style syntax is used throughout this document, but all specifications also apply to the equivalent LSL functions and event callbacks.
SNEP is primarily useful in two situations:
However, SNEP can be used over any medium; use of HTTP or CLEP is not mandatory for compliance with the SNEP recommendation.
SNEP messages must be JSON objects with the following key-value pairs:
| Key | Required? | Type | Value |
|---|---|---|---|
snep -> sign_algo |
REQUIRED | "RSA" or "HMAC" | If "RSA", the signature is an RSA signature. If "HMAC", the signature is an HMAC signature. |
snep -> hash_algo |
REQUIRED | "md5" (HMAC only), "sha1", "sha224", "sha256", "sha384", or "sha512" | MUST be the hash algorithm used when signing the message. |
snep -> key_name |
REQUIRED | string | MUST be a string uniquely identifying the key. This is used to allow the recipient to quickly look up the matching key. |
snep -> utime |
REQUIRED | os.time() in Lua llGetUnixTime() in LSL |
MUST be the Unix time when the message was signed. |
snep -> signature |
REQUIRED | string | If sign_algo is "RSA", this MUST be the result of ll.SignRSA(private_key, tostring(utime) .. payload, hash_algo).If sign_algo is "HMAC", this MUST be the result of ll.HMAC(shared_key, tostring(utime) .. payload, hash_algo). |
payload |
REQUIRED | string | MUST be any string. If encapsulating a CLEP message or other JSON, it must be encoded to a string first to ensure that the message does not get accidentally reordered, breaking the signature. |
SNEP is only as secure as the underlying RSA and HMAC implementations. If RSA or HMAC is ever broken, SNEP will also be broken. Scripters SHOULD research known issues with RSA and HMAC before relying on them for security.
SNEP is a signature only; it does not obfuscate the message to any degree and is not considered a form of encryption. Messages sent with SNEP are not private. If the message cannot be disclosed at all, payload can be encrypted before being signed.
RSA and HMAC are especially vulnerable to replay attacks. These attacks can be mitigated in several ways:
utime Check: Messages with an old utime should be discarded as stale. While this is not foolproof, since a dedicated attacker can still replay messages within the same Unix second, this check can be used in combination with a duplication check to reduce its long-term storage requirements.X-SecondLife-Object-Key to provide a guaranteed truthful UUID for this check if validating in an external server.A duplication check and a utime check with a reasonable limit depending on the extent of the network (2-10 seconds is typically sufficient) is the RECOMMENDED method to mitigate replay attacks.
Which signature algorithm should I use, RSA or HMAC?
RSA is asymmetric; the keys used by the sender and recipient are not the same, and the sender's private key cannot be derived from knowing the recipient's public key. RSA provides an arguably higher level of security, because if the recipient's public key is disclosed, the sender's ability to sign messages with the private key is not compromised.
HMAC is symmetric; the same key must be used when signing and verifying. Its benefit is that its signatures are smaller (24-88 characters, depending on the hash algorithm, compared to 172-684 characters for RSA, for 1024-4096 bit keys) and it takes less time to generate and verify signatures (approximately 1 frame or ~0.022s, compared to approximately 3 frames or ~0.067s for RSA in testing). The downside is that if the shared key is exposed on either end, signatures may be forged. Therefore, HMAC should never be used in modifiable scripts or stored anywhere that could be exposed to a third party.
Which hash algorithm should I use?
"md5" and "sha1" are considered insecure and SHOULD NOT be used. Other SHA algorithms are considered safe, but larger ones confer better security against collisions. There does not appear to be any difference in performance when testing different hash algorithms.
ll.SignRSA() supports "sha1", "sha224", "sha256", "sha384", or "sha512". There is no difference in processing time or the size of the resulting signature, so scripts SHOULD use "sha512" when signing with RSA.
ll.HMAC() supports all the same SHA algorithms, plus "md5". Each algorithm results in a different signature size:
hash_algo |
Base64 Characters |
|---|---|
| "md5" | 24 (trivially broken) |
| "sha1" | 28 (broken) |
| "sha224" | 40 |
| "sha256" | 44 |
| "sha384" | 64 |
| "sha512" | 88 |
"sha512" is RECOMMENDED for HMAC, but if the shortest possible signature is necessary, a smaller hash MAY be used.
What key length should I use for my RSA keys?
Testing shows that 1024 and 2048 bit keys are typically fast, signing and verifying in a few frames at most. 4096 bit keys take significantly longer to sign - about a quarter of a second. Signature sizes also change depending on the key length:
| Key Length | Base64 Characters |
|---|---|
| 1024 | 172 |
| 2048 | 344 |
| 4096 | 684 |
Key lengths of at least 2048 are currently RECOMMENDED. Key lengths below 1024 bits are generally considered insecure.
SNEP was authored by Nelson Jenkins on behalf of GSI.