Proton BASIC Compiler - Encryption: Skipjack


  • Pic® Basic


  • Encryption: Skipjack

    In the process of building a secure bootloader for micros. I needed a simple, yet secure, block cipher to allow end-to-end encrypted data to be transported to the micro, actual decryption takes place inside the micro itself, not the loader.
    So, I've decided to share my Proton BASIC implementation of the U.S. NSA's Skipjack Encryption algorithm.
    Skipjack is a secure block cipher, it utilizes an 80-bit keyspace, and works on 64-bit blocks of data. It is a 32 round unbalanced Feistel network.
    Background Info @ Wikipedia, Full Specifications, Cryptanalysis.

    Cryptanalysis has shown that the best attacks on the algorithm fair no better than brute force, making this algorithm more secure than DES.

    My particular implementation was focused on reducing the code footprint due to its intended use in a bootloader, code space is precious.
    Also, the code has been optimized in such a way so it compiles as small as possible, this makes the code more difficult to read, but by no means is it unmanageable.
    It makes use of quite a lot of RAM and is suited for 18F devices. I reasoned that I could afford to consume the RAM during bootloader operation, as it would be invalidated once the application code started running.

    Currently the compile stats are as follows using the latest compiler (Beta as of this writing 3.5.1.5):
    RAM Required: 2,861 bytes
    Code Size: (Encrypt AND Decrypt) 1,242 bytes
    Code Size: (Encrypt OR Decrypt) 842 bytes
    This doesn't include assigning the key, data, or any of the demo related code, only the algorithm with 256 byte F Table.

    Check the source code header or the full specification for test vectors.

    I'm releasing this under the MIT License, feel free to do with it what you will.
    If you use this in a commercial project and/or find it really useful, karma dictates you buy me a beer.

    Code:
    '****************************************************************
    '*     Name : SKIPJACK_TINYCODE.BAS                [SKIPJACKtc] *
    '*   Author : Phil Ciebiera                                     *
    '*   Notice : Copyright ©2011 Phil Ciebiera                     *
    '*     Date : 2/22/2011                                         *
    '*  Revised : 3/12/2011                                         *
    '*  Version : 2.2.137                                           *
    '******************************INFO******************************
    '* Implements the Skipjack algorithm present in the             *
    '* Clipper chip developed by the U.S. NSA.                      *
    '* Read More:                                                   *
    '* Wikipedia [http://bit.ly/13sHSf]                             *
    '* Full Spec @ nist.gov [http://1.usa.gov/fcuUR9]               *
    '* Cryptanalysis of Skipjack [http://bit.ly/ezLfUi]             *
    '*                                                              *
    '* This version is optimized to reduce the code footprint as    *
    '* much as possible. The tradeoff being RAM usage is increased. *
    '* Ideal for use in secure Bootloaders.                         *
    '*                                                              *
    '* RAM Required: 2,861 bytes                                    *
    '*    Code Size: (Encrypt AND Decrypt) 1,242 bytes              *
    '*    Code Size: (Encrypt OR Decrypt)    842 bytes              *
    '*                                                              *
    '* Test Vector:                                                 *
    '*         Key: 0x00998877665544332211                          *
    '*  Plain Text: 0x33221100DDCCBBAA                              *
    '* Cipher Text: 0x2587CAE27A12D300                              *
    '************************MIT**LICENSE****************************                                  
    '* Copyright (C) 2011 by Phil Ciebiera                          *
    '*                                                              *
    '* Permission is hereby granted, free of charge, to any person  *
    '* obtaining a copy of this software and associated             *
    '* documentation files (the "Software"), to deal in the         *
    '* Software without restriction, including without limitation   *
    '* the rights to use, copy, modify, merge, publish, distribute, *
    '* sublicense, and/or sell copies of the Software, and to       *
    '* permit persons to whom the Software is furnished to do so,   *
    '* subject to the following conditions:                         *
    '*                                                              *
    '* The above copyright notice and this permission notice shall  *
    '* be included in all copies or substantial portions of the     *
    '* Software.                                                    *
    '*                                                              *
    '* Except as contained in this notice, the name(s) of the above *
    '* copyright holders shall not be used in advertising or        *
    '* otherwise to promote the sale, use or other dealings in this *
    '* Software without prior written authorization.                *
    '*                                                              *
    '* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY    *
    '* KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE   *
    '* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR      *
    '* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS   *
    '* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
    '* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR   *
    '* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE    *
    '* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.       *
    '****************************************************************
     
    ' --- BEGIN: DEVICE CONFIG RELATED ---
    Declare Optimiser_Level = 3
    Device 18F46K22
    Xtal 16
    All_Digital True
    Config_Start
        FOSC = INTIO67  ' Internal oscillator block, port function on RA6 and RA7
        PLLCFG = On     ' Oscillator multiplied by 4  
        PRICLKEN = On   ' Primary clock is always enabled
        FCMEN = Off     ' Fail-Safe Clock Monitor disabled
        IESO = Off      ' Oscillator Switchover mode disabled
        PWRTEN = Off    ' Power up timer disabled
        BOREN = Off     ' Brown-out Reset disabled in hardware and software
        WDTEN = Off     ' Watch dog timer is always disabled. SWDTEN has no effect.
        WDTPS = 1       ' 1:1
        CCP2MX = PORTB3 ' CCP2 input/output is multiplexed with RB3
        PBADEN = Off    ' PORTB<5:0> pins are configured as digital I/O on Reset 
        CCP3MX = PORTB5 ' P3A/CCP3 input/output is multiplexed with RB5  
        HFOFST = On     ' HFINTOSC output and ready status are not delayed by the oscillator stable status
        T3CMX = PORTC0  ' T3CKI is on RC0
        P2BMX = PORTD2  ' P2B is on RD2
        MCLRE = EXTMCLR ' MCLR pin enabled, RE3 input pin disabled  
        STVREN = Off    ' Stack full/underflow will not cause Reset
        LVP = On        ' Single-Supply ICSP enabled if MCLRE is also 1
        XINST = Off     ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode) 
        Debug = Off     ' Disabled
        CP0 = Off       ' Block 0 (000800-003FFFh) not code-protected  
        CP1 = Off       ' Block 1 (004000-007FFFh) not code-protected
        CP2 = Off       ' Block 2 (008000-00BFFFh) not code-protected  
        CP3 = Off       ' Block 3 (00C000-00FFFFh) not code-protected
        CPB = Off       ' Boot block (000000-0007FFh) not code-protected
        CPD = Off       ' Data EEPROM not code-protected  
        WRT0 = Off      ' Block 0 (000800-003FFFh) not write-protected
        WRT1 = Off      ' Block 1 (004000-007FFFh) not write-protected  
        WRT2 = Off      ' Block 2 (008000-00BFFFh) not write-protected  
        WRT3 = Off      ' Block 3 (00C000-00FFFFh) not write-protected
        WRTC = Off      ' Configuration registers (300000-3000FFh) not write-protected  
        WRTB = Off      ' Boot Block (000000-0007FFh) not write-protected
        WRTD = Off      ' Data EEPROM not write-protected  
        EBTR0 = Off     ' Block 0 (000800-003FFFh) not protected from table reads executed in other blocks
        EBTR1 = Off     ' Block 1 (004000-007FFFh) not protected from table reads executed in other blocks  
        EBTR2 = Off     ' Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks  
        EBTR3 = Off     ' Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks
        EBTRB = Off     ' Boot Block (000000-0007FFh) not protected from table reads executed in other blocks  
    Config_End 
    ' Configure oscillator
    OSCCON.4 = 1 '
    OSCCON.5 = 1 ' 16MHZ
    OSCCON.6 = 1 '
    ' --- END: DEVICE CONFIG RELATED ---
     
     
    ' --- BEGIN: SKIPJACK RELATED ---
    ' Used for RAM access
    Dim FSRPointer0 As FSR0L.Word
    Dim FSRPointer1 As FSR1L.Word
     
    ' Various temporary working variables
    Dim SJ_wSub1 As Word
    Dim SJ_wSub2 As Word
    Dim SJ_bSub1 As Byte
    Dim SJ_bSub2 As Byte
    Dim SJ_bSub3 As Byte
    Dim SJ_bSub4 As Byte
    Dim SJ_bSub5 As Byte
    Dim SJ_bRound As Byte
    Dim SJ_bSteppingRule As Byte
    Dim SJ_bG_KeyTableOrder[5] As Byte
    Dim SJ_bCurrentVariation As Byte
    Dim SJ_bCurrentWorkItem As Byte
    Dim SJ_bCurrentManipulationItem As Byte
     
    ' This holds the block for encryption/decryption
    Dim SJ_WORK[4] As Word
     
    ' This holds our 80-bit key
    Dim SJ_KEY[10] As Byte
     
    ' These are working tables that are built from the chosen key.
    ' It's very memory hungry, but this is optimized to reduce the code footprint
    Dim tab[256] As Byte
    Dim tab0[256] As Byte
    Dim tab1[256] As Byte
    Dim tab2[256] As Byte
    Dim tab3[256] As Byte
    Dim tab4[256] As Byte
    Dim tab5[256] As Byte
    Dim tab6[256] As Byte
    Dim tab7[256] As Byte
    Dim tab8[256] As Byte
    Dim tab9[256] As Byte
    ' --- PAUSE: SKIPJACK RELATED ---
     
     
    ' --- BEGIN: DEMO RELATED ---
    ' Calculated Baudrate = 9592 @ Xtal 16MHz, Error = -0.08%
    RCSTA     = 144 ' Enable continuous receive
    TXSTA     = 36  ' Enable transmit, BRGH = 1
    SPBRG     = 160 ' Baud Rate Generator Low Byte Value
    SPBRGH    = 1   ' Baud Rate Generator High Byte Value
    BAUDCTL.3 = 1   ' Enable the 16 bit Baud Rate Generator
     
    ' Assign our Key
    SJ_KEY[0] = $00
    SJ_KEY[1] = $99
    SJ_KEY[2] = $88
    SJ_KEY[3] = $77
    SJ_KEY[4] = $66
    SJ_KEY[5] = $55
    SJ_KEY[6] = $44
    SJ_KEY[7] = $33
    SJ_KEY[8] = $22
    SJ_KEY[9] = $11
     
    ' Assign our plaintext for encryption
    SJ_WORK[0] = $3322
    SJ_WORK[1] = $1100
    SJ_WORK[2] = $DDCC
    SJ_WORK[3] = $BBAA
     
    ' Wait for a bit
    DelayMS 3000
     
    HSerOut ["Generating Key Tables [",Hex2 SJ_KEY[0],Hex2 SJ_KEY[1],Hex2 SJ_KEY[2],Hex2 SJ_KEY[3],Hex2 SJ_KEY[4],Hex2 SJ_KEY[5],Hex2 SJ_KEY[6],Hex2 SJ_KEY[7],Hex2 SJ_KEY[8],Hex2 SJ_KEY[9],"]..."]
     
    ' Generate key tables, you only need to do this once, before encrypting or decrypting
    GoSub SJ_MakeKeyTables
     
    HSerOut ["Done!",13,10,"Encrypting ",Hex4 SJ_WORK[0],Hex4 SJ_WORK[1],Hex4 SJ_WORK[2],Hex4 SJ_WORK[3],"..."]
     
    ' Call the encryption function to work on the data in SJ_WORK[0-3] using the key in SJ_KEY[0-9]
    GoSub SJ_Encrypt
     
    HSerOut ["Done!",13,10,"Encrypted Data: ",Hex4 SJ_WORK[0],Hex4 SJ_WORK[1],Hex4 SJ_WORK[2],Hex4 SJ_WORK[3],13,10,"Decrypting ",Hex4 SJ_WORK[0],Hex4 SJ_WORK[1],Hex4 SJ_WORK[2],Hex4 SJ_WORK[3],"..."]
     
    ' Call the decryption function to work on the data in SJ_WORK[0-3] using the key in SJ_KEY[0-9]
    GoSub SJ_Decrypt
     
    HSerOut ["Done!",13,10,"Decrypted Data: ",Hex4 SJ_WORK[0],Hex4 SJ_WORK[1],Hex4 SJ_WORK[2],Hex4 SJ_WORK[3],13,10,13,10,"STOP"]
     
    Stop
    ' --- END: DEMO RELATED ---
     
     
    ' --- RESUME: SKIPJACK RELATED ---
    ' Generates the precomputed key tables in RAM, this is done in RAM to minimize the code footprint
    SJ_MakeKeyTables:
        SJ_bSub1 = 0
        SJ_wSub1 = VarPtr tab0
        Repeat
            SJ_bSub2 = SJ_KEY[SJ_bSub1]
            SJ_bSub3 = 0
            Repeat
                SJ_bSub4 = SJ_bSub3 ^ SJ_bSub2 
                SJ_bSub5 = CRead FTable + SJ_bSub4
                FSRPointer0 = SJ_wSub1
                POSTINC0 = SJ_bSub5
                Inc SJ_wSub1
                Inc SJ_bSub3
            Until SJ_bSub3 = 0
            Inc SJ_bSub1
        Until SJ_bSub1 = 10
    Return
     
    ' Copies one of the key tables to the working table
    CopyKeyTable:
        FSRPointer1 = VarPtr tab
        SJ_wSub1 = VarPtr tab0
        SJ_wSub1 = SJ_wSub1 + (256 * SJ_bSub3)
        FSRPointer0 = SJ_wSub1
        SJ_wSub1 = 0
        Repeat
            POSTINC1 = POSTINC0
            Inc SJ_wSub1
        Until SJ_wSub1 = 256
    Return
     
    ' Encrypts the data at SJ_WORK[0-3] with the key at SJ_KEY[0-9]
    SJ_Encrypt:
        ' Intialize the temporary working variables into their proper state
        SJ_bRound = 1
        SJ_bG_KeyTableOrder[0] = 0
        SJ_bG_KeyTableOrder[1] = 4
        SJ_bG_KeyTableOrder[2] = 8
        SJ_bG_KeyTableOrder[3] = 2
        SJ_bG_KeyTableOrder[4] = 6
        SJ_bCurrentVariation = 0
        SJ_bCurrentWorkItem = 0
        SJ_bSteppingRule = 1
        SJ_bCurrentManipulationItem = 3
     
        Repeat
            If SJ_bSteppingRule = 2 Then SJ_WORK[SJ_bCurrentManipulationItem] = SJ_WORK[SJ_bCurrentManipulationItem] ^ (SJ_WORK[SJ_bCurrentWorkItem] ^ SJ_bRound) ' Stepping B Manipulations
            SJ_bSub3 = SJ_bG_KeyTableOrder[SJ_bCurrentVariation]    ' Assign the correct starting key table for our G function
            SJ_wSub2 = SJ_WORK[SJ_bCurrentWorkItem]                 ' Assign the holding register into the worker variable
     
            ' Run the G Permutation
            SJ_bSub1 = 0
        G_Top:    
            Inc SJ_bSub1
            GoSub CopyKeyTable
            SJ_wSub2 = SJ_wSub2 ^ (tab[SJ_wSub2.LowByte] << 8)
            Inc SJ_bSub3
            GoSub CopyKeyTable
            SJ_wSub2 = SJ_wSub2 ^ (tab[SJ_wSub2.HighByte])
            Inc SJ_bSub3
            If SJ_bSub3 = 10 Then SJ_bSub3 = 0
            If SJ_bSub1 <> 2 Then GoTo G_Top
     
            SJ_WORK[SJ_bCurrentWorkItem] = SJ_wSub2 ' Assign the worked variable back into the holding register
            If SJ_bSteppingRule = 1 Then SJ_WORK[SJ_bCurrentManipulationItem] = SJ_WORK[SJ_bCurrentManipulationItem] ^ (SJ_WORK[SJ_bCurrentWorkItem] ^ SJ_bRound) ' Stepping A Manipulations
     
            ' Increments the various work counters
            Inc SJ_bCurrentVariation                                                ' Increment the G variation on each round
            If SJ_bCurrentVariation = 5 Then SJ_bCurrentVariation = 0               ' Ensure that we start over at G0 after completing G4
            If SJ_bCurrentManipulationItem = 0 Then SJ_bCurrentManipulationItem = 4 ' Ensure that we don't overflow the manipulation item counter
            Dec SJ_bCurrentManipulationItem                                         ' Decrement the manipulation item counter
            If SJ_bCurrentWorkItem = 0 Then SJ_bCurrentWorkItem = 4                 ' Ensure that we don't overflow the G function input counter
            Dec SJ_bCurrentWorkItem                                                 ' Decrement the G function input counter
     
            If SJ_bRound // 8 = 0 Then              ' Change the stepping rule every 8 rounds
                If SJ_bSteppingRule = 1 Then        ' Currently, we're Rule A stepping
                    SJ_bSteppingRule = 2            ' Switch to Rule B stepping
                    SJ_bCurrentManipulationItem = 1 ' Reset the manipulation item counter for the next set of rounds
                Else                                ' Otherwise, we're currently Rule B stepping
                    SJ_bSteppingRule = 1            ' Switch to Rule A stepping
                    SJ_bCurrentManipulationItem = 3 ' Reset the manipulation item counter for the next set of rounds
                EndIf
            EndIf
            Inc SJ_bRound                           ' Increment the round counter
        Until SJ_bRound = 33                        ' Loop until we've completed 32 rounds
     
    Return
     
    ' Decrypts the data at SJ_WORK[0-3] with the key at SJ_KEY[0-9]
    SJ_Decrypt:
        ' Intialize the temporary working variables into their proper state
        SJ_bRound = 32
        SJ_bG_KeyTableOrder[0] = 3
        SJ_bG_KeyTableOrder[1] = 7
        SJ_bG_KeyTableOrder[2] = 1
        SJ_bG_KeyTableOrder[3] = 5
        SJ_bG_KeyTableOrder[4] = 9
        SJ_bCurrentVariation = 1
        SJ_bCurrentWorkItem = 1
        SJ_bSteppingRule = 1
        SJ_bCurrentManipulationItem = 2
     
        Repeat
            If SJ_bSteppingRule = 2 Then SJ_WORK[SJ_bCurrentManipulationItem] = SJ_WORK[SJ_bCurrentManipulationItem] ^ (SJ_WORK[SJ_bCurrentWorkItem] ^ SJ_bRound) ' Stepping B Manipulations
            SJ_bSub3 = SJ_bG_KeyTableOrder[SJ_bCurrentVariation]    ' Assign the correct starting key table for our H function
            SJ_wSub2 = SJ_WORK[SJ_bCurrentWorkItem]                 ' Assign the holding register into the worker variable
     
            ' Run the H permutation
            SJ_bSub1 = 0
        H_Top:
            Inc SJ_bSub1
            GoSub CopyKeyTable
            SJ_wSub2 = SJ_wSub2 ^ (tab[SJ_wSub2.HighByte])
            Dec SJ_bSub3
            GoSub CopyKeyTable
            SJ_wSub2 = SJ_wSub2 ^ (tab[SJ_wSub2.LowByte] << 8)
            If SJ_bSub3 = 0 Then SJ_bSub3 = 10
            Dec SJ_bSub3
            If SJ_bSub1 <> 2 Then GoTo H_Top
     
     
            SJ_WORK[SJ_bCurrentWorkItem] = SJ_wSub2  ' Assign the worked variable back into the holding register
            If SJ_bSteppingRule = 1 Then SJ_WORK[SJ_bCurrentManipulationItem] = SJ_WORK[SJ_bCurrentManipulationItem] ^ (SJ_WORK[SJ_bCurrentWorkItem] ^ SJ_bRound) ' Stepping A Manipulations
     
            If SJ_bCurrentVariation = 0 Then SJ_bCurrentVariation = 5               ' Ensure that we start over at H4 after completing H0
            Dec SJ_bCurrentVariation                                                ' Decrement the H variation on each round
            Inc SJ_bCurrentManipulationItem                                         ' Increment the manipulation item counter
            If SJ_bCurrentManipulationItem = 4 Then SJ_bCurrentManipulationItem = 0 ' Ensure that we don't overflow the manipulation item counter
            Inc SJ_bCurrentWorkItem                                                 ' Increment the H function Input Counter
            If SJ_bCurrentWorkItem = 4 Then SJ_bCurrentWorkItem = 0                 ' Ensure that we don't overflow the H function input counter
     
            Dec SJ_bRound                           ' Decrement the round counter
     
            If SJ_bRound // 8 = 0 Then              ' Change the stepping rule every 8 rounds
                If SJ_bSteppingRule = 1 Then        ' Currently, we're Rule A stepping
                    SJ_bSteppingRule = 2            ' Switch to Rule B stepping
                    SJ_bCurrentManipulationItem = 0 ' Reset the manipulation item counter for the next set of rounds
                Else                                ' Otherwise, we're currently Rule B stepping
                    SJ_bSteppingRule = 1            ' Switch to Rule A stepping
                    SJ_bCurrentManipulationItem = 2 ' Reset the manipulation item counter for the next set of rounds
                EndIf
            EndIf
        Until SJ_bRound = 0                         ' Loop until we've completed 32 rounds in reverse order
     
    Return
     
     
    ' F Table, this is specified by the Skipjack algorithm
    FTable:
    CData $A3, $D7, $09, $83, $F8, $48, $F6, $F4, $B3, $21, $15, $78, $99, $B1, $AF, $F9
    CData $E7, $2D, $4D, $8A, $CE, $4C, $CA, $2E, $52, $95, $D9, $1E, $4E, $38, $44, $28
    CData $0A, $DF, $02, $A0, $17, $F1, $60, $68, $12, $B7, $7A, $C3, $E9, $FA, $3D, $53
    CData $96, $84, $6B, $BA, $F2, $63, $9A, $19, $7C, $AE, $E5, $F5, $F7, $16, $6A, $A2
    CData $39, $B6, $7B, $0F, $C1, $93, $81, $1B, $EE, $B4, $1A, $EA, $D0, $91, $2F, $B8
    CData $55, $B9, $DA, $85, $3F, $41, $BF, $E0, $5A, $58, $80, $5F, $66, $0B, $D8, $90
    CData $35, $D5, $C0, $A7, $33, $06, $65, $69, $45, $00, $94, $56, $6D, $98, $9B, $76
    CData $97, $FC, $B2, $C2, $B0, $FE, $DB, $20, $E1, $EB, $D6, $E4, $DD, $47, $4A, $1D
    CData $42, $ED, $9E, $6E, $49, $3C, $CD, $43, $27, $D2, $07, $D4, $DE, $C7, $67, $18
    CData $89, $CB, $30, $1F, $8D, $C6, $8F, $AA, $C8, $74, $DC, $C9, $5D, $5C, $31, $A4
    CData $70, $88, $61, $2C, $9F, $0D, $2B, $87, $50, $82, $54, $64, $26, $7D, $03, $40
    CData $34, $4B, $1C, $73, $D1, $C4, $FD, $3B, $CC, $FB, $7F, $AB, $E6, $3E, $5B, $A5
    CData $AD, $04, $23, $9C, $14, $51, $22, $F0, $29, $79, $71, $7E, $FF, $8C, $0E, $E2
    CData $0C, $EF, $BC, $72, $75, $6F, $37, $A1, $EC, $D3, $8E, $62, $8B, $86, $10, $E8
    CData $08, $77, $11, $BE, $92, $4F, $24, $C5, $32, $36, $9D, $CF, $F3, $A6, $BB, $AC
    CData $5E, $6C, $A9, $13, $57, $25, $B5, $E3, $BD, $A8, $3A, $01, $05, $59, $2A, $46
    ' --- END: SKIPJACK RELATED ---
    Results:
  • Recent Activity

    BobS-112245

    Adin Problem, PIC16F753

    Thread Starter: BobS

    Firstly, my apologies if the presentation is a little messy, its my first posting. I have written a small program (see listing below) for the...

    BobS Today, 17:15 Go to last post
    charliecoultas-16125

    Peripheral Pin Remapping

    Thread Starter: charliecoultas

    Guys I need a little help. I get an error "COUT_PIN_RP140 Not found" and "CIN_PIN_RP141 Not found" with this setup: Device = 33EP512GP806 ...

    charliecoultas Today, 17:08 Go to last post