g | x | w | all
Bytes Lang Time Link
nanScala 3240510T114217Z138 Aspe
557Rust240510T131221Zmousetai
569Python 3140307T132931ZMichael
nanPython240510T120204Z138 Aspe
111Pyth170924T130412ZEndingBe
330Ruby140307T014848ZDoorknob

Scala 3, 1492 766 747 740 bytes

A port of @Doorknob's Ruby answer in Scala.

Saved 726+19+7=752 bytes thanks to @ceilingcat


Golfed version. Attempt This Online!

object m{def t()=scala.io.StdIn.readLine().trim.replace('j','i').toUpperCase.filter(_.isLetter).toList
def f(t:Array[Array[Char]],c:Char):(Int,Int)={t.zipWithIndex.collectFirst{case(r,i)if r.contains(c)=>(i,r.indexOf(c))}.getOrElse((0,0))}
def main(A:Array[String])={val k=t().distinct
var F=(k++("ABCDEFGHIKLMNOPQRSTUVWXYZ".filterNot(k.contains))).toArray
val T=Array.tabulate(5,5)((r,c)=>F(r*5+c))
val s=(List[Char]()/:t()){(a,c)=>if(a.nonEmpty&&a.last==c)a:+'X':+c else a:+c}
println(s"${(if(s.size%2<1)s else s:+'X').sliding(2,2).toList.flatMap{p=>val(g,h)=(p(0),p.last)
val(r,c)=f(T,g)
val(q,d)=f(T,h)
if(r==q){List(T(r)((c+1)%5),T(q)((d+1)%5))}else if(c==d){List(T((r+1)%5)(c),T((q+1)%5)(d))}else{List(T(r)(d),T(q)(c))}}.mkString}")}}

Ungolfed version. Attempt This Online!

object Main {
  // Function to transform the input string
  def transform(text: String): List[Char] = {
    // Replace 'j' with 'i', convert to uppercase, remove non-alphabet characters, and split into characters
    text.replace('j', 'i').toUpperCase.filter(_.isLetter).toList
  }

  // Function to generate the Playfair cipher key table
  def generateKeyTable(key: String): Array[Array[Char]] = {
    val keyChars = transform(key).distinct
    val remainingChars = "ABCDEFGHIKLMNOPQRSTUVWXYZ".filterNot(keyChars.contains)
    val combinedChars = (keyChars ++ remainingChars).toArray
    // Create the 5x5 key table
    Array.tabulate(5, 5)((row, col) => combinedChars(row * 5 + col))
  }

  // Function to prepare the message for encryption
  def prepareMessage(msg: String): List[List[Char]] = {
    val msgChars = transform(msg)
    val preparedMsg = msgChars.foldLeft(List[Char]()) { (acc, char) =>
      if (acc.nonEmpty && acc.last == char) acc :+ 'X' :+ char else acc :+ char
    }
    val evenPreparedMsg = if (preparedMsg.size % 2 == 0) preparedMsg else preparedMsg :+ 'X'
    evenPreparedMsg.sliding(2, 2).toList
  }

  // Function to find coordinates of a character in the key table
  def findCoords(tbl: Array[Array[Char]], char: Char): (Int, Int) = {
    tbl.zipWithIndex.collectFirst {
      case (row, rowIndex) if row.contains(char) => (rowIndex, row.indexOf(char))
    }.getOrElse(throw new NoSuchElementException(s"Character $char not found in the table"))
  }

  // Function to encrypt a message using the Playfair cipher
  def encryptMessage(tbl: Array[Array[Char]], msg: List[List[Char]]): String = {
    msg.flatMap { pair =>
      val (c1, c2) = (pair.head, pair.last)
      val (c1Row, c1Col) = findCoords(tbl, c1)
      val (c2Row, c2Col) = findCoords(tbl, c2)
      if (c1Row == c2Row) {
        // Same row
        List(tbl(c1Row)((c1Col + 1) % 5), tbl(c2Row)((c2Col + 1) % 5))
      } else if (c1Col == c2Col) {
        // Same column
        List(tbl((c1Row + 1) % 5)(c1Col), tbl((c2Row + 1) % 5)(c2Col))
      } else {
        // Rectangle swap
        List(tbl(c1Row)(c2Col), tbl(c2Row)(c1Col))
      }
    }.mkString
  }

  def main(args: Array[String]): Unit = {
    val key = scala.io.StdIn.readLine().trim
    val msg = scala.io.StdIn.readLine().trim

    // Generate the key table and prepare the message
    val keyTable = generateKeyTable(key)
    val preparedMsg = prepareMessage(msg)

    // Encrypt the message
    val encryptedMsg = encryptMessage(keyTable, preparedMsg)

    // Output the encrypted message
    println(s"Encrypted Message: $encryptedMsg")
  }
}

Rust, 583 578 bytes 557 bytes

use std::io::*;fn f(mut k:Vec<u8>,t:Vec<u8>){let u=|a:Vec<u8>|a.into_iter().flat_map(|mut a|{a|=32;(a>96&&a<123).then(||a-(a==106)as u8)});k.extend(97..123);let mut o=vec![];for a in u(k){if!o.contains(&a){o.push(a)}}let mut p=vec![];for c in u(t){if p.len()%2>0&&p[p.len()-1]==c{p.push(120)}p.push(c)}if p.len()%2>0{p.push(120)}for k in 0..p.len()/2{let[d,e]=[0,1].map(|z|o.iter().position(|&a|a==p[k*2+z]).unwrap());stdout().write(&if d%5==e%5{[(d+5)%25,(e+5)%25]}else if d/5==e/5{[d/5*5+(d+1)%5,e/5*5+(e+1)%5]}else{[d/5*5+e%5,e/5*5+d%5]}.map(|z|o[z]));}}

Attempt This Online!

Formatted

use std::io::*;
fn f(mut k: Vec<u8>, t: Vec<u8>) {
    let u = |a: Vec<u8>| {
        a.into_iter().flat_map(|mut a| {
            a |= 32;
            (a > 96 && a < 123).then(|| a - (a == 106) as u8)
        })
    };
    k.extend(97..123);
    let mut o = vec![];
    for a in u(k) {
        if !o.contains(&a) {
            o.push(a)
        }
    }
    let mut p = vec![];
    for c in u(t) {
        if p.len() % 2 > 0 && p[p.len() - 1] == c {
            p.push(120)
        }
        p.push(c)
    }
    if p.len() % 2 > 0 {
        p.push(120)
    }
    for k in 0..p.len() / 2 {
        let [d, e] = [0, 1].map(|z| o.iter().position(|&a| a == p[k * 2 + z]).unwrap());
        stdout().write(
            &if d % 5 == e % 5 {
                [(d + 5) % 25, (e + 5) % 25]
            } else if d / 5 == e / 5 {
                [d / 5 * 5 + (d + 1) % 5, e / 5 * 5 + (e + 1) % 5]
            } else {
                [d / 5 * 5 + e % 5, e / 5 * 5 + d % 5]
            }
            .map(|z| o[z]),
        );
    }
}

Python 3, 709 705 685 664 662 615 569

With @ceilingcat's improvements. Accepts input from stdin.

import itertools as I,re,string
a=string.ascii_uppercase
d=lambda x:I.product(range(5),repeat=x)
t=lambda x:(input()+x).upper().replace('J','I')
s=''
for _ in t(a):
 if _ not in s and _ in a:s+=_
m=[s[i:i+5]for i in range(0,len(s),5)]
e={r[i]+r[j]:r[-~i%5]+r[-~j%5]for r in m for i,j in d(2)if i-j}
e.update({c[i]+c[j]:c[-~i%5]+c[-~j%5]for c in zip(*m)for i,j in d(2)if i-j})
e.update({m[q][r]+m[u][v]:m[q][v]+m[u][r]for q,r,u,v in d(4)if(q-u)*(r-v)})
print(''.join(e[a+(b if b else'X')]for a,b in re.findall(r'(.)(?:(?!\1)(.))?',''.join([_ for _ in t('')if _ in a]))))

Example:

mfukar@oxygen[/tmp]<>$ python playfair.py
Stack Overflow
The cat crept into the crypt, crapped, and crept out again.
SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY

Python, 1338 525 bytes

A port of @Doorknob's Ruby answer in Python.

Saved 813 bytes thanks to @ceilingcat


Golfed version. Try It Online!

L=len
t=lambda:[c for c in input().strip().replace('j','i').upper()if c.isalpha()]
u=list(dict.fromkeys(t()))
m=t()
j=0
p=e=''
while j<L(m):
 p+=m[j];j+=1
 if j<L(m)and m[j-1]==m[j]:p+='X'
if L(p)%2:p+='X'
l=[(u+[h for h in"ABCDEFGHIKLMNOPQRSTUVWXYZ"if h not in u])[j:j+5]for j in range(0,25,5)]
def f(h):
 for i,o in enumerate(l):
  if h in o:return i,o.index(h)
for c,C in[p[j:j+2]for j in range(0,L(p),2)]:r,i=f(c);R,I=f(C);e+=l[r][-~i%5]+l[R][-~I%5]if r==R else l[r][I]+l[R][i]if i-I else l[-~r%5][i]+l[-~R%5][I]
print(e)

Ungolfed version. Attempt This Online!

# Function to transform the input string
def transform(text):
    # Replace 'j' with 'i', convert to uppercase, remove non-alphabet characters, and split into characters
    text = text.replace('j', 'i').upper()
    return [char for char in text if char.isalpha()]

# Function to generate the Playfair cipher key table
def generate_key_table(key):
    key = transform(key)
    # Create a list of unique characters in the key followed by the rest of the alphabet (excluding 'J')
    unique_key_chars = list(dict.fromkeys(key))
    remaining_chars = [char for char in "ABCDEFGHIKLMNOPQRSTUVWXYZ" if char not in unique_key_chars]
    combined_chars = unique_key_chars + remaining_chars
    # Create the 5x5 key table
    return [combined_chars[i:i+5] for i in range(0, 25, 5)]

# Function to prepare the message for encryption
def prepare_message(msg):
    msg = transform(msg)
    # Add 'X' between repeating characters
    prepared_msg = []
    i = 0
    while i < len(msg):
        prepared_msg.append(msg[i])
        if i + 1 < len(msg) and msg[i] == msg[i + 1]:
            prepared_msg.append('X')
        i += 1
    # Ensure even length by adding 'X' if necessary
    if len(prepared_msg) % 2 != 0:
        prepared_msg.append('X')
    # Split into pairs
    return [prepared_msg[i:i+2] for i in range(0, len(prepared_msg), 2)]

# Function to find coordinates of a character in the key table
def find_coords(tbl, char):
    for row_idx, row in enumerate(tbl):
        if char in row:
            return row_idx, row.index(char)
    return None

# Function to encrypt a message using the Playfair cipher
def encrypt_message(tbl, msg):
    encrypted_msg = []
    for pair in msg:
        c1, c2 = pair
        c1_row, c1_col = find_coords(tbl, c1)
        c2_row, c2_col = find_coords(tbl, c2)
        if c1_row == c2_row:
            # Same row
            encrypted_msg.append(tbl[c1_row][(c1_col + 1) % 5])
            encrypted_msg.append(tbl[c2_row][(c2_col + 1) % 5])
        elif c1_col == c2_col:
            # Same column
            encrypted_msg.append(tbl[(c1_row + 1) % 5][c1_col])
            encrypted_msg.append(tbl[(c2_row + 1) % 5][c2_col])
        else:
            # Rectangle swap
            encrypted_msg.append(tbl[c1_row][c2_col])
            encrypted_msg.append(tbl[c2_row][c1_col])
    return ''.join(encrypted_msg)

# Get user input for the key and message
key = input().strip()
msg = input().strip()

# Generate the key table and prepare the message
key_table = generate_key_table(key)
prepared_msg = prepare_message(msg)

# Encrypt the message
encrypted_msg = encrypt_message(key_table, prepared_msg)

# Output the encrypted message
print(encrypted_msg)

Pyth - 111

Too late for competing, I just wanted to share. Here's the encoder and decoder

L@G:rb0\j\iJ{y+wGKywWhZ=Zh*2xlR{RcK2 1IhZ=KXZK\x;M@J+G?!eH5?!hH?q4%G5_4 1eHVcK2A,xJhNxJeN=Z-V.DH5.DG5pgGZpgH_RZ

Explanation:

L    b                              L defines common method y(b); 2 calls helps us saving two bytes
    r 0                             lowercase r(b,0)
   :   \j\i                         : replaces all occurrences of "j" with "i"
 @G                                 strips all non-alphabetic characters; G = pyth built-in alphabet

    w                               first input argument
   + G                              appends the alphabet (G)
  y                                 calls y(b)
 {                                  { makes set (removes duplicated characters)
J                                   assigns result to 'J' (KEY VARIABLE)

Kyw                                 assigns output from y(second input argument) to 'K' (TEXT VARIABLE)

WhZ                         ;       While (Z+1 != 0) <-> While (Z != -1) <-> While mismatched items found
             cK2                    list of K pairs.                    e.g. 'ABCCDDE' -> [AB, CC, DD, E]
         lR{R                       l length of { unique characters.    e.g. [2, 1, 1, 1]
        x       1                   1-length first index.               e.g. 1
     h*2                            *2+1 (Index in K)                   e.g. 3 'ABC CDDE'
   =Z                               Assigns to 'Z'
                  IhZ               if (Z != -1) <-> if (mismatched found)
                     =KXZK\x        X Inserts at Z index in K an 'x' and reassigns to 'K'  e.g. 'ABCXC...'

M                                   M defines function g(G, H) where G index H vector (INDEX CONVERSION)
     ?!eH                           if (same col)
         5                              then +5
         ?!hH                           else { if (same row)
             ?q4%G5                             then if (last col)
                   _4                               then -4
                      1                             else +1
                       eH                       else col
   +G                               index += increment
 @J                                 J[index]

VcK2                                V loops over cK2 list of K pairs
     ,xJhNxJeN                      x returns pair members index in J
    A                               A assigns G = xJhN, H = xJeN
                  .DH5              .D returns [row, col] = [i/5,i%5] of 5xn matrix from index of H
                      .DG5          idem. of G
                -V                  Subtracts vectors (RELATIVE POSITION)
              =Z                    Assigns to 'Z'
                          pgGZ          p prints g(G, Z) return value
                              pgH_RZ    p prints g(H, _RZ) return value, and _R changes signs of Z vector

Sample Key/Message/Output:

Stack Overflow
Gottfried Leibniz is famous for his slogan Calculemus, which means Let us calculate. He envisioned a formal language to reduce reasoning to calculation.
lfaukvvnrbbomwpmupkoexvqkovfimaqohflcmkcdsqwbxqtlintinbehcbovttksbtybsavmormwuthrhrbkevfxebqbspdxtbfsvfrwyarfrctrhmpwkrssbtybsvurh

Ruby, 461 411 366 359 352 346 330 characters

k,m=$<.map{|x|x.tr(?j,?i).upcase.tr('^A-Z','').chars}
t=[*((k&k)|[*?A..?Z]-[?J]).each_slice(5)]
m=(m*'').gsub(/(.)\1/,'\1X\1').chars
c=->n{[t.index{|r|k=r.index n},k]}
$><<(m.size%2<1?m:m+[?X]).each_slice(2).map{|p,q|a,b,d,e=*c[p],*c[q]
a==d ?[t[a][(b+1)%5],t[d][(e+1)%5]]:b==e ?[t[(a+1)%5][b],t[(d+1)%5][e]]:[t[a][e],t[d][b]]}*''

Thanks to @daniero for saving... err, a lot of bytes. \o/

Here's the ungolfed code:

key = gets.chomp
msg = gets.chomp
transform = ->str{
    str.gsub! 'j', 'i'
    str.upcase!
    str.gsub! /[^A-Z]/, ''
    str.split('')
}

# 1. Generate a key table
key = transform[key]
chars = key.uniq + ([*?A..?Z] - key - ['J'])
tbl = Array.new(5) {
    Array.new(5) {
        chars.shift
    }
}

# 2. Prepare the message
msg = transform[msg]
msg = msg.join('').gsub(/(.)\1/){ "#{$1}X#{$1}" }.split('')
msg = (msg.length % 2 == 0 ? msg : msg + ['X']).each_slice(2).to_a

# 3. Encryption
coords = ->chr{
    i = -1
    [tbl.index{|row| i = row.index chr}, i]
}
msg.map! do |c1, c2|
    c1, c2 = coords[c1], coords[c2]
    if c1[0] == c2[0]
        # same row
        [tbl[c1[0]][(c1[1] + 1) % 5], tbl[c2[0]][(c2[1] + 1) % 5]]
    elsif c1[1] == c2[1]
        # same column
        [tbl[(c1[0] + 1) % 5][c1[1]], tbl[(c2[0] + 1) % 5][c2[1]]]
    else
        # neither
        [tbl[c1[0]][c2[1]], tbl[c2[0]][c1[1]]]
    end
end

# Output!
puts msg.join

Here's some sample outputs:

llama@llama:...code/ruby/ppcg23276playfair$ printf 'Stack Overflow\nThe cat crept into the crypt, crapped, and crept out again.\n' | ./playfair.rb; printf 'This is a password!\nProgramming Puzzles and Code Golf is a Stack Exchange site.\n' | ./playfair.rb
SIRAVXRDFMVUUYVSBLRDZNYVECMZMFBCYNRDFMSVTVKBVBMY
WDDEDSXIXOQFBTUYVQFISQWGRPFBWMESATAHHGMBVEITQFFISHMI