Criptare e decriptare dinamicamente il codice è la parte facile - anche nel codice macchina, è tutto solo un mucchio di byte; potete fare quello che volete (a patto di notificare al sistema operativo le vostre intenzioni). Per esempio guardate qui - Vladislav Zorov'risposta al C (linguaggio di programmazione): Puoi scrivere un programma C per dimostrare un codice auto modificante?
Nascondere la chiave di crittografia è molto più difficile. Ci sono schemi crittografici speciali per questo, chiamati crittografia White-box - l'idea è che la chiave non è memorizzata come dati, ma è implicita nella struttura di un pezzo di codice complesso.
Naturalmente, tutto questo non serve a nulla se si può semplicemente eseguire il programma fino a quando non si è decrittato, e poi scaricare tutto. Quindi ha bisogno di decifrare, eseguire e ricifrare continuamente.
Come ulteriore impedimento, si può creare la propria CPU, con un set di istruzioni progettato per essere particolarmente ottuso. E poi basta compilare il programma per quello, e farlo eseguire in una macchina virtuale.
Ci saranno anche meccanismi per rilevare l'esecuzione in un debugger o in una macchina virtuale, per rendere difficile l'analisi dinamica del codice.
Ovviamente, niente di tutto ciò funziona davvero - rende solo il reverse engineering più lento e doloroso. Se ci fosse una soluzione, avremmo un DRM funzionante, e chiaramente non ce l'abbiamo.
P.S. Presumo che tu non intenda davvero "codice sorgente", ma solo "codice". Il codice sorgente generalmente non è incluso nell'applicazione, tranne che nel software libero/open source, dove impedire alla gente di vederlo è l'esatto contrario di quello che si sta cercando di fare.