RFC 6238

TOTP: Time-Based One-Time Password Algorithm

๐Ÿ“… published May 2011, from ietf

Internet Engineering Task Force (IETF) ๐ŸŒธ

RFC: 6238 ๐Ÿ’– Title: TOTP: Time-Based One-Time Password Algorithm โœจ Authors: D. M'Raihi (Verisign, Inc.), S. Machani (Diversinet Corp.), M. Pei (Symantec), J. Rydell (Portwise, Inc.) ๐Ÿซถ Category: Informational ๐Ÿ“š ISSN: 2070-1721 Date: May 2011 ๐Ÿ—“๏ธ


Abstract ๐ŸŽ€

Okay bestie, this doc is a glow-up of HOTP (the One-Time Password algorithm from RFC 4226) ๐Ÿ’…. It adds a time-based moving factor. HOTP counts events, but this one counts time, which gives you short-lived OTP values. Those are seriously desirable for security ๐Ÿ”.

It can be used across a wide range of network apps, from remote VPN access and Wi-Fi logon to transaction-oriented web apps ๐ŸŒ. The authors believe a common, shared algorithm will help two-factor authentication take off on the Internet, because commercial and open-source implementations can actually talk to each other ๐Ÿฆ‹.


Status of This Memo ๐Ÿ“

This document is not an Internet Standards Track specification. It's published for informational purposes ๐Ÿงš.

It's a product of the Internet Engineering Task Force (IETF) and represents the consensus of the IETF community. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG). Not every IESG-approved document is a candidate for any level of Internet Standard, so see Section 2 of RFC 5741 for that ๐Ÿ’€.

Info about the current status, any errata, and how to send feedback lives at http://www.rfc-editor.org/info/rfc6238 ๐Ÿ”—.


Copyright (c) 2011 IETF Trust and the persons identified as the document authors. All rights reserved ๐Ÿ’œ.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info), as in effect on the date of publication. Please review those documents carefully, because they describe your rights and restrictions for this document. Code components extracted from this document must include Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they're provided without warranty as described in the Simplified BSD License ๐Ÿ™ˆ.


Table of Contents ๐Ÿ—‚๏ธ

   1. Introduction ....................................................2
      1.1. Scope ......................................................2
      1.2. Background .................................................3
   2. Notation and Terminology ........................................3
   3. Algorithm Requirements ..........................................3
   4. TOTP Algorithm ..................................................4
      4.1. Notations ..................................................4
      4.2. Description ................................................4
   5. Security Considerations .........................................5
      5.1. General ....................................................5
      5.2. Validation and Time-Step Size ..............................6
   6. Resynchronization ...............................................7
   7. Acknowledgements ................................................7
   8. References ......................................................8
      8.1. Normative References .......................................8
      8.2. Informative References .....................................8
   Appendix A. TOTP Algorithm: Reference Implementation ...............9
   Appendix B. Test Vectors ..........................................14

1. Introduction ๐ŸŒˆ

1.1. Scope ๐Ÿ”

Okay so the main idea: this document extends the One-Time Password (OTP) algorithm, specifically the HMAC-based One-Time Password (HOTP) algorithm defined in [RFC4226], so that it uses a time-based moving factor ๐Ÿ•ฐ๏ธ.

1.2. Background ๐Ÿง 

As [RFC4226] defines, HOTP is based on the HMAC-SHA-1 algorithm (as specified in [RFC2104]). It's applied to an increasing counter value, which represents the message in the HMAC computation ๐Ÿ”ข.

Basically, the HMAC-SHA-1 output gets truncated so you end up with user-friendly values:

HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))

Here, Truncate is the function that converts an HMAC-SHA-1 value into an HOTP value. K is the shared secret and C is the counter value. For detailed definitions, check [RFC4226] ๐Ÿค“.

TOTP is the time-based version of this algorithm. A value T, derived from a time reference and a time step, takes the place of the counter C in the HOTP computation โณ.

TOTP implementations MAY use HMAC-SHA-256 or HMAC-SHA-512 functions, based on the SHA-256 or SHA-512 [SHA2] hash functions. They can do that instead of the HMAC-SHA-1 function specified for the HOTP computation in [RFC4226] ๐Ÿฆ‹.

2. Notation and Terminology ๐Ÿ“–

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 [RFC2119]. Those are the official vibes-check words, so read them carefully ๐Ÿ’….

3. Algorithm Requirements ๐Ÿ“‹

This section sums up the requirements the team considered when designing the TOTP algorithm ๐Ÿง.

  • R1: The prover (e.g., token, soft token) and verifier (authentication or validation server) MUST know or be able to derive the current Unix time (i.e., the number of seconds elapsed since midnight UTC of January 1, 1970) for OTP generation. See [UT] for a more detailed definition of the commonly known "Unix time". The precision of the time used by the prover affects how often the clock synchronization should be done; see Section 6 โฑ๏ธ.
  • R2: The prover and verifier MUST either share the same secret or the knowledge of a secret transformation to generate a shared secret ๐Ÿค.
  • R3: The algorithm MUST use HOTP [RFC4226] as a key building block ๐Ÿงฑ.
  • R4: The prover and verifier MUST use the same time-step value X ๐ŸŽฏ.
  • R5: There MUST be a unique secret (key) for each prover ๐Ÿ”‘.
  • R6: The keys SHOULD be randomly generated or derived using key derivation algorithms ๐ŸŽฒ.
  • R7: The keys MAY be stored in a tamper-resistant device and SHOULD be protected against unauthorized access and usage ๐Ÿ›ก๏ธ.

4. TOTP Algorithm โฐ

This variant of the HOTP algorithm calculates a one-time password value. It does this by representing the counter as a time factor, and that's the whole trick ๐Ÿช„.

4.1. Notations ๐Ÿ“

  • X is the time step in seconds (default value X = 30 seconds). It's a system parameter โฒ๏ธ.
  • T0 is the Unix time to start counting time steps (default value is 0, i.e., the Unix epoch). It's also a system parameter ๐ŸŒ….

4.2. Description โœจ

Basically, we define TOTP as:

TOTP = HOTP(K, T)

where T is an integer representing the number of time steps between the initial counter time T0 and the current Unix time. It's giving math, but it's simple, promise ๐Ÿ’–.

More specifically:

T = (Current Unix time - T0) / X

The default floor function is used in that computation ๐Ÿงฎ.

For example, with T0 = 0 and Time Step X = 30, T = 1 if the current Unix time is 59 seconds, and T = 2 if the current Unix time is 60 seconds ๐Ÿ“.

The implementation of this algorithm MUST support a time value T larger than a 32-bit integer when it's beyond the year 2038. Hello, future-proofing ๐Ÿš€. The values of the system parameters X and T0 are pre-established during the provisioning process and communicated between the prover and verifier as part of the provisioning step. The provisioning flow is out of scope for this document, so refer to [RFC6030] for those provisioning container specifications ๐Ÿ“ฆ.

5.2. Validation and Time-Step Size โœจ

Okay so, an OTP generated within the same time step will be the same ๐Ÿฆ‹. When an OTP lands at a validation system, it doesn't know the client's exact timestamp for when it was generated ๐Ÿ˜ญ. So the validation system may typically use the timestamp from when the OTP is received for the comparison ๐Ÿ’…. Because of network latency, the gap (measured by T, which is the number of time steps since T0) between when the OTP was generated and when it arrives can be huge ๐Ÿข. That means the receiving time and the actual generation might not fall in the same time-step window that produced the same OTP. If an OTP is generated at the very end of a time-step window, the receiving time most likely lands in the next one ๐Ÿ’€.

A validation system SHOULD typically set a policy for an acceptable OTP transmission delay window for validation. The validation system should compare OTPs not only with the receiving timestamp but also with the past timestamps that are within the transmission delay. A larger acceptable delay window would expose a larger window for attacks ๐Ÿšจ. We RECOMMEND that at most one time step is allowed as the network delay.

The time-step size has an impact on both security and usability ๐ŸŽ€. A larger time-step size means a larger validity window for an OTP to be accepted by a validation system. There are implications for using a larger time-step size, as follows:

First, a larger time-step size exposes a larger window to attack ๐Ÿ•ต๏ธโ€โ™€๏ธ. When an OTP is generated and exposed to a third party before it is consumed, the third party can consume the OTP within the time-step window.

We RECOMMEND a default time-step size of 30 seconds ๐ŸŒธ. This default value of 30 seconds is selected as a balance between security and usability โš–๏ธ.

Second, the next different OTP must be generated in the next time-step window โฐ. A user must wait until the clock moves to the next time-step window from the last submission. The waiting time may not be exactly the length of the time step, depending on when the last OTP was generated. For example, if the last OTP was generated at the halfway point in a time-step window, the waiting time for the next OTP is half the length of the time step ๐Ÿ™ˆ. In general, a larger time-step window means a longer waiting time for a user to get the next valid OTP after the last successful OTP validation. A too-large window (for example, 10 minutes) most probably won't be suitable for typical Internet login use cases ๐Ÿ™…โ€โ™€๏ธ. A user may not be able to get the next OTP within 10 minutes and therefore will have to re-login to the same site in 10 minutes ๐Ÿ˜ฉ.

Note that a prover may send the same OTP inside a given time-step window multiple times to a verifier ๐Ÿ“. The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP. This ensures one-time only use of an OTP ๐Ÿ”โœจ.


6. Resynchronization โœจ

Because clocks can drift between a client and a validation server, we RECOMMEND that the validator be set with a specific limit on the number of time steps a prover can be "out of synch" before being rejected ๐Ÿ•ฐ๏ธ.

This limit can be set both forward and backward from the calculated time step, based on receipt of the OTP value. If the time step is 30 seconds as recommended, and the validator is set to only accept two time steps backward, then the maximum elapsed time drift would be around 89 seconds ๐Ÿงฎ. That's 29 seconds in the calculated time step plus 60 seconds for two backward time steps.

This would mean the validator could perform a validation against the current time and then two further validations for each backward step, for a total of 3 validations ๐Ÿ”. Upon successful validation, the validation server can record the detected clock drift for the token in terms of the number of time steps ๐Ÿ“. When a new OTP is received after this step, the validator can validate the OTP with the current timestamp adjusted by the recorded number of time-step clock drifts for the token ๐Ÿ’œ.

Also, it is important to note that the longer a prover has not sent an OTP to a validation system, the longer (potentially) the accumulated clock drift between the prover and the verifier becomes ๐Ÿซ . In such cases, the automatic resynchronization described above may not work if the drift exceeds the allowed threshold ๐Ÿ˜ฌ. Additional authentication measures should be used to safely authenticate the prover and explicitly resynchronize the clock drift between the prover and the validator ๐Ÿ”’๐Ÿซถ.


7. Acknowledgements ๐ŸŒˆ

The authors of this document would like to thank the following people for their contributions and support to make this a better specification: Hannes Tschofenig, Jonathan Tuliani, David Dix, Siddharth Bajaj, Stu Veath, Shuh Chang, Oanh Hoang, John Huang, and Siddhartha Mohapatra ๐Ÿ‘‘๐Ÿ™Œ.


8. References ๐Ÿ“š

8.1. Normative References

  • [RFC2104] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed-Hashing for Message Authentication", RFC 2104, February 1997.
  • [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
  • [RFC4086] Eastlake 3rd, D., Schiller, J., and S. Crocker, "Randomness Recommendations for Security", BCP 106, RFC 4086, June 2005.
  • [RFC4226] M'Raihi, D., Bellare, M., Hoornaert, F., Naccache, D., and O. Ranen, "HOTP: An HMAC-Based One-Time Password Algorithm", RFC 4226, December 2005.
  • [SHA2] NIST, "FIPS PUB 180-3: Secure Hash Standard (SHS)", October 2008, <http://csrc.nist.gov/publications/fips/fips180-3/fips180-3_final.pdf>.

8.2. Informative References

Appendix A. TOTP Algorithm: Reference Implementation โœจ

okay so this is the big finale ๐ŸŽ€ Appendix A is a full Java reference implementation of the OATH TOTP algorithm. It's an example, not a spec, but it's the code you can actually run and compare your own output against ๐Ÿ’… It has HMAC-SHA1, HMAC-SHA256 and HMAC-SHA512 versions, and a main method that prints a test table for the RFC's test times ๐Ÿงš

<CODE BEGINS>

/**
Copyright (c) 2011 IETF Trust and the persons identified as
authors of the code. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, is permitted pursuant to, and subject to the license
terms contained in, the Simplified BSD License set forth in Section
4.c of the IETF Trust's Legal Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info).
*/

import java.lang.reflect.UndeclaredThrowableException;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.util.TimeZone;


/**
 * This is an example implementation of the OATH
 * TOTP algorithm.
 * Visit www.openauthentication.org for more information.
 *
 * @author Johan Rydell, PortWise, Inc.
 */

public class TOTP {

    private TOTP() {}

    /**
     * This method uses the JCE to provide the crypto algorithm.
     * HMAC computes a Hashed Message Authentication Code with the
     * crypto hash algorithm as a parameter.
     *
     * @param crypto: the crypto algorithm (HmacSHA1, HmacSHA256,
     *                             HmacSHA512)
     * @param keyBytes: the bytes to use for the HMAC key
     * @param text: the message or text to be authenticated
     */

    private static byte[] hmac_sha(String crypto, byte[] keyBytes,
            byte[] text){
        try {
            Mac hmac;
            hmac = Mac.getInstance(crypto);
            SecretKeySpec macKey =
                new SecretKeySpec(keyBytes, "RAW");
            hmac.init(macKey);
            return hmac.doFinal(text);
        } catch (GeneralSecurityException gse) {
            throw new UndeclaredThrowableException(gse);
        }
    }


    /**
     * This method converts a HEX string to Byte[]
     *
     * @param hex: the HEX string
     *
     * @return: a byte array
     */

    private static byte[] hexStr2Bytes(String hex){
        // Adding one byte to get the right conversion
        // Values starting with "0" can be converted
        byte[] bArray = new BigInteger("10" + hex,16).toByteArray();

        // Copy all the REAL bytes, not the "first"
        byte[] ret = new byte[bArray.length - 1];
        for (int i = 0; i < ret.length; i++)
            ret[i] = bArray[i+1];
        return ret;
    }

    private static final int[] DIGITS_POWER
    // 0 1  2   3    4     5      6       7        8
    = {1,10,100,1000,10000,100000,1000000,10000000,100000000 };
    /**
     * This method generates a TOTP value for the given
     * set of parameters.
     *
     * @param key: the shared secret, HEX encoded
     * @param time: a value that reflects a time
     * @param returnDigits: number of digits to return
     *
     * @return: a numeric String in base 10 that includes
     *              {@link truncationDigits} digits
     */

    public static String generateTOTP(String key,
            String time,
            String returnDigits){
        return generateTOTP(key, time, returnDigits, "HmacSHA1");
    }


    /**
     * This method generates a TOTP value for the given
     * set of parameters.
     *
     * @param key: the shared secret, HEX encoded
     * @param time: a value that reflects a time
     * @param returnDigits: number of digits to return
     *
     * @return: a numeric String in base 10 that includes
     *              {@link truncationDigits} digits
     */

    public static String generateTOTP256(String key,
            String time,
            String returnDigits){
        return generateTOTP(key, time, returnDigits, "HmacSHA256");
    }
    /**
     * This method generates a TOTP value for the given
     * set of parameters.
     *
     * @param key: the shared secret, HEX encoded
     * @param time: a value that reflects a time
     * @param returnDigits: number of digits to return
     *
     * @return: a numeric String in base 10 that includes
     *              {@link truncationDigits} digits
     */

    public static String generateTOTP512(String key,
            String time,
            String returnDigits){
        return generateTOTP(key, time, returnDigits, "HmacSHA512");
    }


    /**
     * This method generates a TOTP value for the given
     * set of parameters.
     *
     * @param key: the shared secret, HEX encoded
     * @param time: a value that reflects a time
     * @param returnDigits: number of digits to return
     * @param crypto: the crypto function to use
     *
     * @return: a numeric String in base 10 that includes
     *              {@link truncationDigits} digits
     */

    public static String generateTOTP(String key,
            String time,
            String returnDigits,
            String crypto){
        int codeDigits = Integer.decode(returnDigits).intValue();
        String result = null;

        // Using the counter
        // First 8 bytes are for the movingFactor
        // Compliant with base RFC 4226 (HOTP)
        while (time.length() < 16 )
            time = "0" + time;

        // Get the HEX in a Byte[]
        byte[] msg = hexStr2Bytes(time);
        byte[] k = hexStr2Bytes(key);
        byte[] hash = hmac_sha(crypto, k, msg);

        // put selected bytes into result int
        int offset = hash[hash.length - 1] & 0xf;

        int binary =
            ((hash[offset] & 0x7f) << 24) |
            ((hash[offset + 1] & 0xff) << 16) |
            ((hash[offset + 2] & 0xff) << 8) |
            (hash[offset + 3] & 0xff);

        int otp = binary % DIGITS_POWER[codeDigits];

        result = Integer.toString(otp);
        while (result.length() < codeDigits) {
            result = "0" + result;
        }
        return result;
    }

    public static void main(String[] args) {
        // Seed for HMAC-SHA1 - 20 bytes
        String seed = "3132333435363738393031323334353637383930";
        // Seed for HMAC-SHA256 - 32 bytes
        String seed32 = "3132333435363738393031323334353637383930" +
        "313233343536373839303132";
        // Seed for HMAC-SHA512 - 64 bytes
        String seed64 = "3132333435363738393031323334353637383930" +
        "3132333435363738393031323334353637383930" +
        "3132333435363738393031323334353637383930" +
        "31323334";
        long T0 = 0;
        long X = 30;
        long testTime[] = {59L, 1111111109L, 1111111111L,
                1234567890L, 2000000000L, 20000000000L};

        String steps = "0";
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        try {
            System.out.println(
                    "+---------------+-----------------------+" +
            "------------------+--------+--------+");
            System.out.println(
                    "|  Time(sec)    |   Time (UTC format)   " +
            "| Value of T(Hex)  |  TOTP  | Mode   |");
            System.out.println(
                    "+---------------+-----------------------+" +
            "------------------+--------+--------+");

            for (int i=0; i<testTime.length; i++) {
                long T = (testTime[i] - T0)/X;
                steps = Long.toHexString(T).toUpperCase();
                while (steps.length() < 16) steps = "0" + steps;
                String fmtTime = String.format("%1$-11s", testTime[i]);
                String utcTime = df.format(new Date(testTime[i]*1000));
                System.out.print("|  " + fmtTime + "  |  " + utcTime +
                        "  | " + steps + " |");
                System.out.println(generateTOTP(seed, steps, "8",
                "HmacSHA1") + "| SHA1   |");
                System.out.print("|  " + fmtTime + "  |  " + utcTime +
                        "  | " + steps + " |");
                System.out.println(generateTOTP(seed32, steps, "8",
                "HmacSHA256") + "| SHA256 |");
                System.out.print("|  " + fmtTime + "  |  " + utcTime +
                        "  | " + steps + " |");
                System.out.println(generateTOTP(seed64, steps, "8",
                "HmacSHA512") + "| SHA512 |");

                System.out.println(
                        "+---------------+-----------------------+" +
                "------------------+--------+--------+");
            }
        }catch (final Exception e){
            System.out.println("Error : " + e);
        }
    }
}

<CODE ENDS>

and that's the whole reference impl ๐Ÿฆ‹ when you run main, the table it prints is your cheat sheet for checking that your own numbers line up with the RFC's ๐ŸŒธ

Appendix B. Test Vectors ๐ŸŽ€

okay so this part is all about test vectors, and they're literally how implementers check their work ๐Ÿงช this section gives you test values for the HOTP time-based variant algorithm interoperability test, so you can make sure your implementation matches everyone else's. no cap, it's giving proof of work ๐Ÿ’…

the test token shared secret is the ASCII string value "12345678901234567890" ๐Ÿ”‘ with Time Step X = 30 and the Unix epoch as the initial value for counting time steps (T0 = 0), the TOTP algorithm will display the values in the table below for the specified modes and timestamps. bestie, check those numbers against yours ๐Ÿ‘€

  +-------------+--------------+------------------+----------+--------+
  |  Time (sec) |   UTC Time   | Value of T (hex) |   TOTP   |  Mode  |
  +-------------+--------------+------------------+----------+--------+
  |      59     |  1970-01-01  | 0000000000000001 | 94287082 |  SHA1  |
  |             |   00:00:59   |                  |          |        |
  |      59     |  1970-01-01  | 0000000000000001 | 46119246 | SHA256 |
  |             |   00:00:59   |                  |          |        |
  |      59     |  1970-01-01  | 0000000000000001 | 90693936 | SHA512 |
  |             |   00:00:59   |                  |          |        |
  |  1111111109 |  2005-03-18  | 00000000023523EC | 07081804 |  SHA1  |
  |             |   01:58:29   |                  |          |        |
  |  1111111109 |  2005-03-18  | 00000000023523EC | 68084774 | SHA256 |
  |             |   01:58:29   |                  |          |        |
  |  1111111109 |  2005-03-18  | 00000000023523EC | 25091201 | SHA512 |
  |             |   01:58:29   |                  |          |        |
  |  1111111111 |  2005-03-18  | 00000000023523ED | 14050471 |  SHA1  |
  |             |   01:58:31   |                  |          |        |
  |  1111111111 |  2005-03-18  | 00000000023523ED | 67062674 | SHA256 |
  |             |   01:58:31   |                  |          |        |
  |  1111111111 |  2005-03-18  | 00000000023523ED | 99943326 | SHA512 |
  |             |   01:58:31   |                  |          |        |
  |  1234567890 |  2009-02-13  | 000000000273EF07 | 89005924 |  SHA1  |
  |             |   23:31:30   |                  |          |        |
  |  1234567890 |  2009-02-13  | 000000000273EF07 | 91819424 | SHA256 |
  |             |   23:31:30   |                  |          |        |
  |  1234567890 |  2009-02-13  | 000000000273EF07 | 93441116 | SHA512 |
  |             |   23:31:30   |                  |          |        |
  |  2000000000 |  2033-05-18  | 0000000003F940AA | 69279037 |  SHA1  |
  |             |   03:33:20   |                  |          |        |
  |  2000000000 |  2033-05-18  | 0000000003F940AA | 90698825 | SHA256 |
  |             |   03:33:20   |                  |          |        |
  |  2000000000 |  2033-05-18  | 0000000003F940AA | 38618901 | SHA512 |
  |             |   03:33:20   |                  |          |        |
  | 20000000000 |  2603-10-11  | 0000000027BC86AA | 65353130 |  SHA1  |
  |             |   11:33:20   |                  |          |        |
  | 20000000000 |  2603-10-11  | 0000000027BC86AA | 77737706 | SHA256 |
  |             |   11:33:20   |                  |          |        |
  | 20000000000 |  2603-10-11  | 0000000027BC86AA | 47863826 | SHA512 |
  |             |   11:33:20   |                  |          |        |
  +-------------+--------------+------------------+----------+--------+

                            Table 1: TOTP Table

Authors' Addresses ๐Ÿงš

the authors are David M'Raihi, Salah Machani, Mingliang Pei, and Johan Rydell. here's where to find them, the way the RFC lists it ๐Ÿ’œ

   David M'Raihi
   Verisign, Inc.
   685 E. Middlefield Road
   Mountain View, CA  94043
   USA

   EMail: davidietf@gmail.com


   Salah Machani
   Diversinet Corp.
   2225 Sheppard Avenue East, Suite 1801
   Toronto, Ontario  M2J 5C2
   Canada

   EMail: smachani@diversinet.com


   Mingliang Pei
   Symantec
   510 E. Middlefield Road
   Mountain View, CA  94043
   USA

   EMail: Mingliang_Pei@symantec.com


   Johan Rydell
   Portwise, Inc.
   275 Hawthorne Ave., Suite 119
   Palo Alto, CA  94301
   USA

   EMail: johanietf@gmail.com

๐Ÿ“ฆ where this page comes from